Update ExoPlayer version
Bug: 153662231
Test: atest CtsMediaParserTestCases
Change-Id: Ie38c26561310779e96c0386475e0912a80a44e82
diff --git a/Android.bp b/Android.bp
index 6c35394..3d47254 100644
--- a/Android.bp
+++ b/Android.bp
@@ -78,7 +78,7 @@
srcs: [],
sdk_version: "29",
visibility: ["//cts/tests/tests/mediaparser:__subpackages__"],
- asset_dirs: ["tree/library/extractor/src/test/assets"],
+ asset_dirs: ["tree/testdata/src/test/assets/"],
// Do not compress media files.
aaptflags: [
"-0 .ac3",
diff --git a/METADATA b/METADATA
index d9101ec..998c606 100644
--- a/METADATA
+++ b/METADATA
@@ -16,7 +16,7 @@
type: GIT
value: "https://github.com/google/ExoPlayer.git"
}
- version: "r2.10.6"
- last_upgrade_date { year: 2019 month: 10 day: 28 }
+ version: "d33c5ac0b3248a94211d844696652742854f96c0"
+ last_upgrade_date { year: 2020 month: 4 day: 9 }
license_type: NOTICE
}
\ No newline at end of file
diff --git a/tree/.github/ISSUE_TEMPLATE/bug.md b/tree/.github/ISSUE_TEMPLATE/bug.md
new file mode 100644
index 0000000..8824c9e
--- /dev/null
+++ b/tree/.github/ISSUE_TEMPLATE/bug.md
@@ -0,0 +1,57 @@
+---
+name: Bug report
+about: Issue template for a bug report.
+title: ''
+labels: bug, needs triage
+assignees: ''
+---
+
+Before filing a bug:
+-----------------------
+- Search existing issues, including issues that are closed:
+ https://github.com/google/ExoPlayer/issues?q=is%3Aissue
+- Consult our developer website, which can be found at https://exoplayer.dev/.
+ It provides detailed information about supported formats and devices.
+- Learn how to create useful log output by using the EventLogger:
+ https://exoplayer.dev/listening-to-player-events.html#using-eventlogger
+- Rule out issues in your own code. A good way to do this is to try and
+ reproduce the issue in the ExoPlayer demo app. Information about the ExoPlayer
+ demo app can be found here:
+ http://exoplayer.dev/demo-application.html.
+
+When reporting a bug:
+-----------------------
+Fill out the sections below, leaving the headers but replacing the content. If
+you're unable to provide certain information, please explain why in the relevant
+section. We may close issues if they do not include sufficient information.
+
+### [REQUIRED] Issue description
+Describe the issue in detail, including observed and expected behavior.
+
+### [REQUIRED] Reproduction steps
+Describe how the issue can be reproduced, ideally using the ExoPlayer demo app
+or a small sample app that you’re able to share as source code on GitHub.
+
+### [REQUIRED] Link to test content
+Provide a JSON snippet for the demo app’s media.exolist.json file, or a link to
+media that reproduces the issue. If you don't wish to post it publicly, please
+submit the issue, then email the link to dev.exoplayer@gmail.com using a subject
+in the format "Issue #1234", where "#1234" should be replaced with your issue
+number. Provide all the metadata we'd need to play the content like drm license
+urls or similar. If the content is accessible only in certain countries or
+regions, please say so.
+
+### [REQUIRED] A full bug report captured from the device
+Capture a full bug report using "adb bugreport". Output from "adb logcat" or a
+log snippet is NOT sufficient. Please attach the captured bug report as a file.
+If you don't wish to post it publicly, please submit the issue, then email the
+bug report to dev.exoplayer@gmail.com using a subject in the format
+"Issue #1234", where "#1234" should be replaced with your issue number.
+
+### [REQUIRED] Version of ExoPlayer being used
+Specify the absolute version number. Avoid using terms such as "latest".
+
+### [REQUIRED] Device(s) and version(s) of Android being used
+Specify the devices and versions of Android on which the issue can be
+reproduced, and how easily it reproduces. If possible, please test on multiple
+devices and Android versions.
diff --git a/tree/.github/ISSUE_TEMPLATE/feature_request.md b/tree/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..d660d03
--- /dev/null
+++ b/tree/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,30 @@
+---
+name: Feature request
+about: Issue template for a feature request.
+title: ''
+labels: enhancement, needs triage
+assignees: ''
+---
+
+Before filing a feature request:
+-----------------------
+- Search existing open issues, specifically with the label ‘enhancement’:
+ https://github.com/google/ExoPlayer/labels/enhancement
+- Search existing pull requests: https://github.com/google/ExoPlayer/pulls
+
+When filing a feature request:
+-----------------------
+Fill out the sections below, leaving the headers but replacing the content. If
+you're unable to provide certain information, please explain why in the relevant
+section. We may close issues if they do not include sufficient information.
+
+### [REQUIRED] Use case description
+Describe the use case or problem you are trying to solve in detail. If there are
+any standards or specifications involved, please provide the relevant details.
+
+### Proposed solution
+A clear and concise description of your proposed solution, if you have one.
+
+### Alternatives considered
+A clear and concise description of any alternative solutions you considered,
+if applicable.
diff --git a/tree/.github/ISSUE_TEMPLATE/question.md b/tree/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..f3ad83b
--- /dev/null
+++ b/tree/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,50 @@
+---
+name: Question
+about: Issue template for a question.
+title: ''
+labels: question, needs triage
+assignees: ''
+---
+
+Before filing a question:
+-----------------------
+- This issue tracker is intended ExoPlayer specific questions. If you're asking
+ a general Android development question, please do so on Stack Overflow.
+- Search existing issues, including issues that are closed. It’s often the
+ quickest way to get an answer!
+ https://github.com/google/ExoPlayer/issues?q=is%3Aissue
+- Consult our developer website, which can be found at https://exoplayer.dev/.
+ It provides detailed information about supported formats, devices as well as
+ information about how to use the ExoPlayer library.
+- The ExoPlayer library Javadoc can be found at
+ https://exoplayer.dev/doc/reference/
+
+When filing a question:
+-----------------------
+Fill out the sections below, leaving the headers but replacing the content. If
+you're unable to provide certain information, please explain why in the relevant
+section. We may close issues if they do not include sufficient information.
+
+### [REQUIRED] Searched documentation and issues
+Tell us where you’ve already looked for an answer to your question. It’s
+important for us to know this so that we can improve our documentation.
+
+### [REQUIRED] Question
+Describe your question in detail.
+
+### A full bug report captured from the device
+In case your question refers to a problem you are seeing in your app, capture a
+full bug report using "adb bugreport". Please attach the captured bug report as
+a file. If you don't wish to post it publicly, please submit the issue, then
+email the bug report to dev.exoplayer@gmail.com using a subject in the format
+"Issue #1234", where "#1234" should be replaced with your issue number.
+
+### Link to test content
+In case your question is related to a piece of media, which you are trying to
+play, please provide a JSON snippet for the demo app’s media.exolist.json file,
+or a link to media that reproduces the issue. If you don't wish to post it
+publicly, please submit the issue, then email the link to
+dev.exoplayer@gmail.com using a subject in the format "Issue #1234", where
+"#1234" should be replaced with your issue number. Provide all the metadata we'd
+need to play the content like drm license urls or similar. If the content is
+accessible only in certain countries or regions, please say so.
diff --git a/tree/.gitignore b/tree/.gitignore
new file mode 100644
index 0000000..cb4cfaa
--- /dev/null
+++ b/tree/.gitignore
@@ -0,0 +1,77 @@
+# Android generated
+bin
+gen
+libs
+obj
+lint.xml
+
+# IntelliJ IDEA
+.idea
+*.iml
+*.ipr
+*.iws
+classes
+gen-external-apklibs
+
+# Eclipse
+.project
+.classpath
+.settings
+.checkstyle
+.cproject
+
+# Gradle
+.gradle
+build
+buildout
+out
+
+# Maven
+target
+release.properties
+pom.xml.*
+
+# Ant
+ant.properties
+local.properties
+proguard.cfg
+proguard-project.txt
+
+# Bazel
+bazel-bin
+bazel-genfiles
+bazel-out
+bazel-testlogs
+
+# Other
+.DS_Store
+cmake-build-debug
+dist
+tmp
+
+# External native builds
+.externalNativeBuild
+
+# VP9 extension
+extensions/vp9/src/main/jni/libvpx
+extensions/vp9/src/main/jni/libvpx_android_configs
+extensions/vp9/src/main/jni/libyuv
+
+# AV1 extension
+extensions/av1/src/main/jni/cpu_features
+extensions/av1/src/main/jni/libgav1
+
+# Opus extension
+extensions/opus/src/main/jni/libopus
+
+# FLAC extension
+extensions/flac/src/main/jni/flac
+
+# FFmpeg extension
+extensions/ffmpeg/src/main/jni/ffmpeg
+
+# Cronet extension
+extensions/cronet/jniLibs/*
+!extensions/cronet/jniLibs/README.md
+extensions/cronet/libs/*
+!extensions/cronet/libs/README.md
diff --git a/tree/.hgignore b/tree/.hgignore
new file mode 100644
index 0000000..a32dfee
--- /dev/null
+++ b/tree/.hgignore
@@ -0,0 +1,82 @@
+# Mercurial's .hgignore files can only be used in the root directory.
+# You can still apply these rules by adding
+# include:path/to/this/directory/.hgignore to the top-level .hgignore file.
+
+# Ensure same syntax as in .gitignore can be used
+syntax:glob
+
+# Android generated
+bin
+gen
+libs
+obj
+lint.xml
+
+# IntelliJ IDEA & Android Studio
+.idea
+*.iml
+*.ipr
+*.iws
+classes
+gen-external-apklibs
+*.li
+
+# Eclipse
+.project
+.classpath
+.settings
+.checkstyle
+.cproject
+
+# Gradle
+.gradle
+build
+buildout
+out
+
+# Maven
+target
+release.properties
+pom.xml.*
+
+# Ant
+ant.properties
+local.properties
+proguard.cfg
+proguard-project.txt
+
+# Bazel
+bazel-bin
+bazel-genfiles
+bazel-out
+bazel-testlogs
+
+# Other
+.DS_Store
+cmake-build-debug
+dist
+jacoco.exec
+tmp
+
+# VP9 extension
+extensions/vp9/src/main/jni/libvpx
+extensions/vp9/src/main/jni/libvpx_android_configs
+extensions/vp9/src/main/jni/libyuv
+
+# AV1 extension
+extensions/av1/src/main/jni/libgav1
+
+# Opus extension
+extensions/opus/src/main/jni/libopus
+
+# FLAC extension
+extensions/flac/src/main/jni/flac
+
+# FFmpeg extension
+extensions/ffmpeg/src/main/jni/ffmpeg
+
+# Cronet extension
+extensions/cronet/jniLibs/*
+!extensions/cronet/jniLibs/README.md
+extensions/cronet/libs/*
+!extensions/cronet/libs/README.md
diff --git a/tree/LICENSE b/tree/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tree/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/tree/RELEASENOTES.md b/tree/RELEASENOTES.md
index 3536052..4c2284d 100644
--- a/tree/RELEASENOTES.md
+++ b/tree/RELEASENOTES.md
@@ -1,1955 +1,2173 @@
-# Release notes #
+# Release notes
-### dev-v2 (not yet released) ###
+### dev-v2 (not yet released)
-* Core library:
- * Add playlist API ([#6161](https://github.com/google/ExoPlayer/issues/6161)).
- * Add `play` and `pause` methods to `Player`.
- * Add `Player.getCurrentLiveOffset` to conveniently return the live offset.
- * Make `MediaSourceEventListener.LoadEventInfo` and
- `MediaSourceEventListener.MediaLoadData` top-level classes.
- * Rename `MediaCodecRenderer.onOutputFormatChanged` to
- `MediaCodecRenderer.onOutputMediaFormatChanged`, further
- clarifying the distinction between `Format` and `MediaFormat`.
- * Move player message-related constants from `C` to `Renderer`, to avoid
- having the constants class depend on player/renderer classes.
- * Split out `common` and `extractor` submodules.
-* Text:
- * Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
- later).
- * Parse `text-combine-upright` CSS property (i.e. tate-chu-yoko) in WebVTT
- subtitles (rendering is coming later).
-* DRM: Add support for attaching DRM sessions to clear content in the demo app.
-* Downloads: Merge downloads in `SegmentDownloader` to improve overall download
- speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
-* MP4: Store the Android capture frame rate only in `Format.metadata`.
- `Format.frameRate` now stores the calculated frame rate.
-* Testing
- * Upgrade Truth dependency from 0.44 to 1.0.
- * Upgrade to JUnit 4.13-rc-2.
+* Core library:
+ * Add playbackPositionUs parameter to 'LoadControl.shouldContinueLoading'.
+ * The `DefaultLoadControl` default minimum buffer is set to 50 seconds,
+ equal to the default maximum buffer. `DefaultLoadControl` applies the
+ same behavior for audio and video.
+ * Add API in `AnalyticsListener` to report video frame processing offset.
+ `MediaCodecVideoRenderer` reports the event.
+ * Add fields `videoFrameProcessingOffsetUsSum` and
+ `videoFrameProcessingOffsetUsCount` in `DecoderCounters` to compute the
+ average video frame processing offset.
+ * Add playlist API
+ ([#6161](https://github.com/google/ExoPlayer/issues/6161)).
+ * Add `play` and `pause` methods to `Player`.
+ * Add `Player.getCurrentLiveOffset` to conveniently return the live
+ offset.
+ * Add `Player.onPlayWhenReadyChanged` with reasons.
+ * Add `Player.onPlaybackStateChanged` and deprecate
+ `Player.onPlayerStateChanged`.
+ * Add `Player.setAudioSessionId` to set the session ID attached to the
+ `AudioTrack`.
+ * Deprecate and rename `getPlaybackError` to `getPlayerError` for
+ consistency.
+ * Deprecate and rename `onLoadingChanged` to `onIsLoadingChanged` for
+ consistency.
+ * Add `ExoPlayer.setPauseAtEndOfMediaItems` to let the player pause at the
+ end of each media item
+ ([#5660](https://github.com/google/ExoPlayer/issues/5660)).
+ * Split `setPlaybackParameter` into `setPlaybackSpeed` and
+ `AudioComponent.setSkipSilenceEnabled` with callbacks
+ `onPlaybackSpeedChanged` and
+ `AudioListener.onSkipSilenceEnabledChanged`.
+ * Make `MediaSourceEventListener.LoadEventInfo` and
+ `MediaSourceEventListener.MediaLoadData` top-level classes.
+ * Rename `MediaCodecRenderer.onOutputFormatChanged` to
+ `MediaCodecRenderer.onOutputMediaFormatChanged`, further clarifying the
+ distinction between `Format` and `MediaFormat`.
+ * Move player message-related constants from `C` to `Renderer`, to avoid
+ having the constants class depend on player/renderer classes.
+ * Split out `common` and `extractor` submodules.
+ * Allow to explicitly send `PlayerMessage`s at the end of a stream.
+ * Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
+ * Add `DataSpec.customData` to allow applications to pass custom data
+ through `DataSource` chains.
+ * Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
+ and `AudioSink.handleBuffer` to allow batching multiple encoded frames
+ in one buffer.
+ * Add a `Format.Builder` and deprecate all `Format.create*` methods and
+ most `Format.copyWith*` methods.
+ * Split `Format.bitrate` into `Format.averageBitrate` and
+ `Format.peakBitrate`
+ ([#2863](https://github.com/google/ExoPlayer/issues/2863)).
+ * Add option to `MergingMediaSource` to adjust the time offsets between
+ the merged sources
+ ([#6103](https://github.com/google/ExoPlayer/issues/6103)).
+ * `SimpleDecoderVideoRenderer` and `SimpleDecoderAudioRenderer` renamed to
+ `DecoderVideoRenderer` and `DecoderAudioRenderer` respectively, and
+ generalized to work with `Decoder` rather than `SimpleDecoder`.
+ * Add media item based playlist API to Player.
+ * Update `CachedContentIndex` to use `SecureRandom` for generating the
+ initialization vector used to encrypt the cache contents.
+ * Remove deprecated members in `DefaultTrackSelector`.
+ * Add `Player.DeviceComponent` and implement it for `SimpleExoPlayer` so
+ that the device volume can be controlled by player.
+* Text:
+ * Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
+ later).
+ * Parse `text-combine-upright` CSS property (i.e. tate-chu-yoko) in WebVTT
+ subtitles (rendering is coming later).
+ * Parse `tts:combineText` property (i.e. tate-chu-yoko) in TTML subtitles
+ (rendering is coming later).
+ * Fix `SubtitlePainter` to render `EDGE_TYPE_OUTLINE` using the correct
+ color ([#6724](https://github.com/google/ExoPlayer/pull/6724)).
+ * Add support for WebVTT default
+ [text](https://www.w3.org/TR/webvtt1/#default-text-color) and
+ [background](https://www.w3.org/TR/webvtt1/#default-text-background)
+ colors ([PR #4178](https://github.com/google/ExoPlayer/pull/4178),
+ [issue #6581](https://github.com/google/ExoPlayer/issues/6581)).
+ * Parse `tts:ruby` and `tts:rubyPosition` properties in TTML subtitles
+ (rendering is coming later).
+* DRM:
+ * Add support for attaching DRM sessions to clear content in the demo app.
+ * Remove `DrmSessionManager` references from all renderers.
+ `DrmSessionManager` must be injected into the MediaSources using the
+ MediaSources factories.
+ * Add option to inject a custom `DefaultDrmSessionManager` into
+ `OfflineLicenseHelper`
+ ([#7078](https://github.com/google/ExoPlayer/issues/7078)).
+ * Remove generics from DRM components.
+* Downloads: Merge downloads in `SegmentDownloader` to improve overall
+ download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
+* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
+ ([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker is
+ enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`. It may
+ require to scan a significant portion of the file for seeking, which may be
+ costly on large files.
+* MP4: Store the Android capture frame rate only in `Format.metadata`.
+ `Format.frameRate` now stores the calculated frame rate.
+* Testing
+ * Add `TestExoPlayer`, a utility class with APIs to create
+ `SimpleExoPlayer` instances with fake components for testing.
+ * Upgrade Truth dependency from 0.44 to 1.0.
+ * Upgrade to JUnit 4.13-rc-2.
+* UI
+ * Add `showScrubber` and `hideScrubber` methods to DefaultTimeBar.
+ * Move logic of prev, next, fast forward and rewind to ControlDispatcher
+ ([#6926](https://github.com/google/ExoPlayer/issues/6926)).
+* Metadata: Add minimal DVB Application Information Table (AIT) support
+ ([#6922](https://github.com/google/ExoPlayer/pull/6922)).
+* Cast extension: Implement playlist API and deprecate the old queue
+ manipulation API.
+* Demo app: Retain previous position in list of samples.
+* Change the order of extractors for sniffing to reduce start-up latency in
+ `DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory`
+ ([#6410](https://github.com/google/ExoPlayer/issues/6410)).
-### 2.11.2 (TBD) ###
+### 2.11.4 (2020-04-08)
-* Add Java FLAC extractor
- ([#6406](https://github.com/google/ExoPlayer/issues/6406)).
-* Startup latency optimization:
- * Reduce startup latency for DASH and SmoothStreaming playbacks by allowing
- codec initialization to occur before the network connection for the first
- media segment has been established.
- * Reduce startup latency for on-demand DASH playbacks by allowing codec
- initialization to occur before the sidx box has been loaded.
-* Downloads:
- * Fix download resumption when the requirements for them to continue are
- met ([#6733](https://github.com/google/ExoPlayer/issues/6733),
- [#6798](https://github.com/google/ExoPlayer/issues/6798)).
- * Fix `DownloadHelper.createMediaSource` to use `customCacheKey` when creating
- `ProgressiveMediaSource` instances.
-* Metadata:
- * Update `IcyDecoder` to try ISO-8859-1 decoding if UTF-8 decoding fails.
- Also change `IcyInfo.rawMetadata` from `String` to `byte[]` to allow
- developers to handle data that's neither UTF-8 nor ISO-8859-1
- ([#6753](https://github.com/google/ExoPlayer/issues/6753)).
- * Select multiple metadata tracks if multiple metadata renderers are available
- ([#6676](https://github.com/google/ExoPlayer/issues/6676)).
-* UI:
- * Show ad group markers in `DefaultTimeBar` even if they are after the end
- of the current window
- ([#6552](https://github.com/google/ExoPlayer/issues/6552)).
- * Don't use notification chronometer if playback speed is != 1.0
- ([#6816](https://github.com/google/ExoPlayer/issues/6816)).
-* WAV:
- * Support IMA ADPCM encoded data.
- * Improve support for G.711 A-law and mu-law encoded data.
-* MP4: Support "twos" codec (big endian PCM)
- ([#5789](https://github.com/google/ExoPlayer/issues/5789)).
-* FMP4: Add support for encrypted AC-4 tracks.
-* HLS: Fix slow seeking into long MP3 segments
- ([#6155](https://github.com/google/ExoPlayer/issues/6155)).
-* Fix handling of E-AC-3 streams that contain AC-3 syncframes
- ([#6602](https://github.com/google/ExoPlayer/issues/6602)).
-* Fix playback of TrueHD streams in Matroska
- ([#6845](https://github.com/google/ExoPlayer/issues/6845)).
-* Fix MKV subtitles to disappear when intended instead of lasting until the
- next cue ([#6833](https://github.com/google/ExoPlayer/issues/6833)).
-* OkHttp extension: Upgrade OkHttp dependency to 3.12.7, which fixes a class of
- `SocketTimeoutException` issues when using HTTP/2
- ([#4078](https://github.com/google/ExoPlayer/issues/4078)).
-* FLAC extension: Fix handling of bit depths other than 16 in `FLACDecoder`.
- This issue caused FLAC streams with other bit depths to sound like white noise
- on earlier releases, but only when embedded in a non-FLAC container such as
- Matroska or MP4.
-* Add support for ID3 genres added in Wimamp 5.6 (2010).
-
-### 2.11.1 (2019-12-20) ###
-
-* UI: Exclude `DefaultTimeBar` region from system gesture detection
- ([#6685](https://github.com/google/ExoPlayer/issues/6685)).
-* ProGuard fixes:
- * Ensure `Libgav1VideoRenderer` constructor is kept for use by
- `DefaultRenderersFactory`
- ([#6773](https://github.com/google/ExoPlayer/issues/6773)).
- * Ensure `VideoDecoderOutputBuffer` and its members are kept for use by video
- decoder extensions.
- * Ensure raw resources used with `RawResourceDataSource` are kept.
- * Suppress spurious warnings about the `javax.annotation` package, and
- restructure use of `IntDef` annotations to remove spurious warnings about
- `SsaStyle$SsaAlignment`
- ([#6771](https://github.com/google/ExoPlayer/issues/6771)).
-* Fix `CacheDataSource` to correctly propagate `DataSpec.httpRequestHeaders`.
-* Fix issue with `DefaultDownloadIndex` that could result in an
- `IllegalStateException` being thrown from
- `DefaultDownloadIndex.getDownloadForCurrentRow`
- ([#6785](https://github.com/google/ExoPlayer/issues/6785)).
-* Fix `IndexOutOfBoundsException` in `SinglePeriodTimeline.getWindow`
- ([#6776](https://github.com/google/ExoPlayer/issues/6776)).
-* Add missing `@Nullable` to `MediaCodecAudioRenderer.getMediaClock` and
- `SimpleDecoderAudioRenderer.getMediaClock`
- ([#6792](https://github.com/google/ExoPlayer/issues/6792)).
-
-### 2.11.0 (2019-12-11) ###
-
-* Core library:
- * Replace `ExoPlayerFactory` by `SimpleExoPlayer.Builder` and
- `ExoPlayer.Builder`.
- * Add automatic `WakeLock` handling to `SimpleExoPlayer`, which can be enabled
- by calling `SimpleExoPlayer.setHandleWakeLock`
- ([#5846](https://github.com/google/ExoPlayer/issues/5846)). To use this
+* Add `SimpleExoPlayer.setWakeMode` to allow automatic `WifiLock` and
+ `WakeLock` handling
+ ([#6914](https://github.com/google/ExoPlayer/issues/6914)). To use this
feature, you must add the
[WAKE_LOCK](https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK)
permission to your application's manifest file.
- * Add automatic "audio becoming noisy" handling to `SimpleExoPlayer`, which
- can be enabled by calling `SimpleExoPlayer.setHandleAudioBecomingNoisy`.
- * Wrap decoder exceptions in a new `DecoderException` class and report them as
- renderer errors.
- * Add `Timeline.Window.isLive` to indicate that a window is a live stream
- ([#2668](https://github.com/google/ExoPlayer/issues/2668) and
- [#5973](https://github.com/google/ExoPlayer/issues/5973)).
- * Add `Timeline.Window.uid` to uniquely identify window instances.
- * Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be
- set.
- * Deprecate passing the manifest directly to
- `Player.EventListener.onTimelineChanged`. It can be accessed through
- `Timeline.Window.manifest` or `Player.getCurrentManifest()`
- * Add `MediaSource.enable` and `MediaSource.disable` to improve resource
- management in playlists.
- * Add `MediaPeriod.isLoading` to improve `Player.isLoading` state.
- * Fix issue where player errors are thrown too early at playlist transitions
+* Text:
+ * Catch and log exceptions in `TextRenderer` rather than re-throwing. This
+ allows playback to continue even if subtitle decoding fails
+ ([#6885](https://github.com/google/ExoPlayer/issues/6885)).
+ * Allow missing hours and milliseconds in SubRip (.srt) timecodes
+ ([#7122](https://github.com/google/ExoPlayer/issues/7122)).
+* Audio:
+ * Enable playback speed adjustment and silence skipping for floating point
+ PCM audio, via resampling to 16-bit integer PCM. To output the original
+ floating point audio without adjustment, pass `enableFloatOutput=true`
+ to the `DefaultAudioSink` constructor
+ ([#7134](https://github.com/google/ExoPlayer/issues/7134)).
+ * Workaround issue that could cause slower than realtime playback of AAC
+ on Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671).
+ * Fix case where another app spuriously holding transient audio focus
+ could prevent ExoPlayer from acquiring audio focus for an indefinite
+ period of time
+ ([#7182](https://github.com/google/ExoPlayer/issues/7182).
+ * Fix case where the player volume could be permanently ducked if audio
+ focus was released whilst ducking.
+ * Fix playback of WAV files with trailing non-media bytes
+ ([#7129](https://github.com/google/ExoPlayer/issues/7129)).
+ * Fix playback of ADTS files with mid-stream ID3 metadata.
+* DRM:
+ * Fix stuck ad playbacks with DRM protected content
+ ([#7188](https://github.com/google/ExoPlayer/issues/7188)).
+ * Fix playback of Widevine protected content that only provides V1 PSSH
+ atoms on API levels 21 and 22.
+ * Fix playback of PlayReady content on Fire TV Stick (Gen 2).
+* DASH:
+ * Update the manifest URI to avoid repeated HTTP redirects
+ ([#6907](https://github.com/google/ExoPlayer/issues/6907)).
+ * Parse period `AssetIdentifier` elements.
+* HLS: Recognize IMSC subtitles
+ ([#7185](https://github.com/google/ExoPlayer/issues/7185)).
+* UI: Add an option to set whether to use the orientation sensor for rotation
+ in spherical playbacks
+ ([#6761](https://github.com/google/ExoPlayer/issues/6761)).
+* Analytics: Fix `PlaybackStatsListener` behavior when not keeping history
+ ([#7160](https://github.com/google/ExoPlayer/issues/7160)).
+* FFmpeg extension: Add support for `x86_64` architecture.
+* Opus extension: Fix parsing of negative gain values
+ ([#7046](https://github.com/google/ExoPlayer/issues/7046)).
+* Cast extension: Upgrade `play-services-cast-framework` dependency to 18.1.0.
+ This fixes an issue where `RemoteServiceException` was thrown due to
+ `Context.startForegroundService()` not calling `Service.startForeground()`
+ ([#7191](https://github.com/google/ExoPlayer/issues/7191)).
+
+### 2.11.3 (2020-02-19)
+
+* SmoothStreaming: Fix regression that broke playback in 2.11.2
+ ([#6981](https://github.com/google/ExoPlayer/issues/6981)).
+* DRM: Fix issue switching from protected content that uses a 16-byte
+ initialization vector to one that uses an 8-byte initialization vector
+ ([#6982](https://github.com/google/ExoPlayer/issues/6982)).
+
+### 2.11.2 (2020-02-13)
+
+* Add Java FLAC extractor
+ ([#6406](https://github.com/google/ExoPlayer/issues/6406)).
+* Startup latency optimization:
+ * Reduce startup latency for DASH and SmoothStreaming playbacks by
+ allowing codec initialization to occur before the network connection for
+ the first media segment has been established.
+ * Reduce startup latency for on-demand DASH playbacks by allowing codec
+ initialization to occur before the sidx box has been loaded.
+* Downloads:
+ * Fix download resumption when the requirements for them to continue are
+ met ([#6733](https://github.com/google/ExoPlayer/issues/6733),
+ [#6798](https://github.com/google/ExoPlayer/issues/6798)).
+ * Fix `DownloadHelper.createMediaSource` to use `customCacheKey` when
+ creating `ProgressiveMediaSource` instances.
+* DRM: Fix `NullPointerException` when playing DRM protected content
+ ([#6951](https://github.com/google/ExoPlayer/issues/6951)).
+* Metadata:
+ * Update `IcyDecoder` to try ISO-8859-1 decoding if UTF-8 decoding fails.
+ Also change `IcyInfo.rawMetadata` from `String` to `byte[]` to allow
+ developers to handle data that's neither UTF-8 nor ISO-8859-1
+ ([#6753](https://github.com/google/ExoPlayer/issues/6753)).
+ * Select multiple metadata tracks if multiple metadata renderers are
+ available ([#6676](https://github.com/google/ExoPlayer/issues/6676)).
+ * Add support for ID3 genres added in Wimamp 5.6 (2010).
+* UI:
+ * Show ad group markers in `DefaultTimeBar` even if they are after the end
+ of the current window
+ ([#6552](https://github.com/google/ExoPlayer/issues/6552)).
+ * Don't use notification chronometer if playback speed is != 1.0
+ ([#6816](https://github.com/google/ExoPlayer/issues/6816)).
+* HLS: Fix playback of DRM protected content that uses key rotation
+ ([#6903](https://github.com/google/ExoPlayer/issues/6903)).
+* WAV:
+ * Support IMA ADPCM encoded data.
+ * Improve support for G.711 A-law and mu-law encoded data.
+* MP4: Support "twos" codec (big endian PCM)
+ ([#5789](https://github.com/google/ExoPlayer/issues/5789)).
+* FMP4: Add support for encrypted AC-4 tracks.
+* HLS: Fix slow seeking into long MP3 segments
+ ([#6155](https://github.com/google/ExoPlayer/issues/6155)).
+* Fix handling of E-AC-3 streams that contain AC-3 syncframes
+ ([#6602](https://github.com/google/ExoPlayer/issues/6602)).
+* Fix playback of TrueHD streams in Matroska
+ ([#6845](https://github.com/google/ExoPlayer/issues/6845)).
+* Fix MKV subtitles to disappear when intended instead of lasting until the
+ next cue ([#6833](https://github.com/google/ExoPlayer/issues/6833)).
+* OkHttp extension: Upgrade OkHttp dependency to 3.12.8, which fixes a class
+ of `SocketTimeoutException` issues when using HTTP/2
+ ([#4078](https://github.com/google/ExoPlayer/issues/4078)).
+* FLAC extension: Fix handling of bit depths other than 16 in `FLACDecoder`.
+ This issue caused FLAC streams with other bit depths to sound like white
+ noise on earlier releases, but only when embedded in a non-FLAC container
+ such as Matroska or MP4.
+* Demo apps: Add
+ [GL demo app](https://github.com/google/ExoPlayer/tree/dev-v2/demos/gl) to
+ show how to render video to a `GLSurfaceView` while applying a GL shader.
+ ([#6920](https://github.com/google/ExoPlayer/issues/6920)).
+
+### 2.11.1 (2019-12-20)
+
+* UI: Exclude `DefaultTimeBar` region from system gesture detection
+ ([#6685](https://github.com/google/ExoPlayer/issues/6685)).
+* ProGuard fixes:
+ * Ensure `Libgav1VideoRenderer` constructor is kept for use by
+ `DefaultRenderersFactory`
+ ([#6773](https://github.com/google/ExoPlayer/issues/6773)).
+ * Ensure `VideoDecoderOutputBuffer` and its members are kept for use by
+ video decoder extensions.
+ * Ensure raw resources used with `RawResourceDataSource` are kept.
+ * Suppress spurious warnings about the `javax.annotation` package, and
+ restructure use of `IntDef` annotations to remove spurious warnings
+ about `SsaStyle$SsaAlignment`
+ ([#6771](https://github.com/google/ExoPlayer/issues/6771)).
+* Fix `CacheDataSource` to correctly propagate `DataSpec.httpRequestHeaders`.
+* Fix issue with `DefaultDownloadIndex` that could result in an
+ `IllegalStateException` being thrown from
+ `DefaultDownloadIndex.getDownloadForCurrentRow`
+ ([#6785](https://github.com/google/ExoPlayer/issues/6785)).
+* Fix `IndexOutOfBoundsException` in `SinglePeriodTimeline.getWindow`
+ ([#6776](https://github.com/google/ExoPlayer/issues/6776)).
+* Add missing `@Nullable` to `MediaCodecAudioRenderer.getMediaClock` and
+ `SimpleDecoderAudioRenderer.getMediaClock`
+ ([#6792](https://github.com/google/ExoPlayer/issues/6792)).
+
+### 2.11.0 (2019-12-11)
+
+* Core library:
+ * Replace `ExoPlayerFactory` by `SimpleExoPlayer.Builder` and
+ `ExoPlayer.Builder`.
+ * Add automatic `WakeLock` handling to `SimpleExoPlayer`, which can be
+ enabled by calling `SimpleExoPlayer.setHandleWakeLock`
+ ([#5846](https://github.com/google/ExoPlayer/issues/5846)). To use this
+ feature, you must add the
+ [WAKE_LOCK](https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK)
+ permission to your application's manifest file.
+ * Add automatic "audio becoming noisy" handling to `SimpleExoPlayer`,
+ which can be enabled by calling
+ `SimpleExoPlayer.setHandleAudioBecomingNoisy`.
+ * Wrap decoder exceptions in a new `DecoderException` class and report
+ them as renderer errors.
+ * Add `Timeline.Window.isLive` to indicate that a window is a live stream
+ ([#2668](https://github.com/google/ExoPlayer/issues/2668) and
+ [#5973](https://github.com/google/ExoPlayer/issues/5973)).
+ * Add `Timeline.Window.uid` to uniquely identify window instances.
+ * Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always
+ be set.
+ * Deprecate passing the manifest directly to
+ `Player.EventListener.onTimelineChanged`. It can be accessed through
+ `Timeline.Window.manifest` or `Player.getCurrentManifest()`
+ * Add `MediaSource.enable` and `MediaSource.disable` to improve resource
+ management in playlists.
+ * Add `MediaPeriod.isLoading` to improve `Player.isLoading` state.
+ * Fix issue where player errors are thrown too early at playlist
+ transitions ([#5407](https://github.com/google/ExoPlayer/issues/5407)).
+ * Add `Format` and renderer support flags to renderer
+ `ExoPlaybackException`s.
+ * Where there are multiple platform decoders for a given MIME type, prefer
+ to use one that advertises support for the profile and level of the
+ media being played over one that does not, even if it does not come
+ first in the `MediaCodecList`.
+* DRM:
+ * Inject `DrmSessionManager` into the `MediaSources` instead of
+ `Renderers`. This allows each `MediaSource` in a
+ `ConcatenatingMediaSource` to use a different `DrmSessionManager`
+ ([#5619](https://github.com/google/ExoPlayer/issues/5619)).
+ * Add `DefaultDrmSessionManager.Builder`, and remove
+ `DefaultDrmSessionManager` static factory methods that leaked
+ `ExoMediaDrm` instances
+ ([#4721](https://github.com/google/ExoPlayer/issues/4721)).
+ * Add support for the use of secure decoders when playing clear content
+ ([#4867](https://github.com/google/ExoPlayer/issues/4867)). This can be
+ enabled using `DefaultDrmSessionManager.Builder`'s
+ `setUseDrmSessionsForClearContent` method.
+ * Add support for custom `LoadErrorHandlingPolicies` in key and
+ provisioning requests
+ ([#6334](https://github.com/google/ExoPlayer/issues/6334)). Custom
+ policies can be passed via `DefaultDrmSessionManager.Builder`'s
+ `setLoadErrorHandlingPolicy` method.
+ * Use `ExoMediaDrm.Provider` in `OfflineLicenseHelper` to avoid leaking
+ `ExoMediaDrm` instances
+ ([#4721](https://github.com/google/ExoPlayer/issues/4721)).
+* Track selection:
+ * Update `DefaultTrackSelector` to set a viewport constraint for the
+ default display by default.
+ * Update `DefaultTrackSelector` to set text language and role flag
+ constraints for the device's accessibility settings by default
+ ([#5749](https://github.com/google/ExoPlayer/issues/5749)).
+ * Add option to set preferred text role flags using
+ `DefaultTrackSelector.ParametersBuilder.setPreferredTextRoleFlags`.
+* LoadControl:
+ * Default `prioritizeTimeOverSizeThresholds` to false to prevent OOM
+ errors ([#6647](https://github.com/google/ExoPlayer/issues/6647)).
+* Android 10:
+ * Set `compileSdkVersion` to 29 to enable use of Android 10 APIs.
+ * Expose new `isHardwareAccelerated`, `isSoftwareOnly` and `isVendor`
+ flags in `MediaCodecInfo`
+ ([#5839](https://github.com/google/ExoPlayer/issues/5839)).
+ * Add `allowedCapturePolicy` field to `AudioAttributes` to allow to
+ configuration of the audio capture policy.
+* Video:
+ * Pass the codec output `MediaFormat` to `VideoFrameMetadataListener`.
+ * Fix byte order of HDR10+ static metadata to match CTA-861.3.
+ * Support out-of-band HDR10+ dynamic metadata for VP9 in WebM/Matroska.
+ * Assume that protected content requires a secure decoder when evaluating
+ whether `MediaCodecVideoRenderer` supports a given video format
+ ([#5568](https://github.com/google/ExoPlayer/issues/5568)).
+ * Fix Dolby Vision fallback to AVC and HEVC.
+ * Fix early end-of-stream detection when using video tunneling, on API
+ level 23 and above.
+ * Fix an issue where a keyframe was rendered rather than skipped when
+ performing an exact seek to a non-zero position close to the start of
+ the stream.
+* Audio:
+ * Fix the start of audio getting truncated when transitioning to a new
+ item in a playlist of Opus streams.
+ * Workaround broken raw audio decoding on Oppo R9
+ ([#5782](https://github.com/google/ExoPlayer/issues/5782)).
+ * Reconfigure audio sink when PCM encoding changes
+ ([#6601](https://github.com/google/ExoPlayer/issues/6601)).
+ * Allow `AdtsExtractor` to encounter EOF when calculating average frame
+ size ([#6700](https://github.com/google/ExoPlayer/issues/6700)).
+* Text:
+ * Add support for position and overlapping start/end times in SSA/ASS
+ subtitles ([#6320](https://github.com/google/ExoPlayer/issues/6320)).
+ * Require an end time or duration for SubRip (SRT) and SubStation Alpha
+ (SSA/ASS) subtitles. This applies to both sidecar files & subtitles
+ [embedded in Matroska streams](https://matroska.org/technical/specs/subtitles/index.html).
+* UI:
+ * Make showing and hiding player controls accessible to TalkBack in
+ `PlayerView`.
+ * Rename `spherical_view` surface type to `spherical_gl_surface_view`.
+ * Make it easier to override the shuffle, repeat, fullscreen, VR and small
+ notification icon assets
+ ([#6709](https://github.com/google/ExoPlayer/issues/6709)).
+* Analytics:
+ * Remove `AnalyticsCollector.Factory`. Instances should be created
+ directly, and the `Player` should be set by calling
+ `AnalyticsCollector.setPlayer`.
+ * Add `PlaybackStatsListener` to collect `PlaybackStats` for analysis and
+ analytics reporting.
+* DataSource
+ * Add `DataSpec.httpRequestHeaders` to support setting per-request headers
+ for HTTP and HTTPS.
+ * Remove the `DataSpec.FLAG_ALLOW_ICY_METADATA` flag. Use is replaced by
+ setting the `IcyHeaders.REQUEST_HEADER_ENABLE_METADATA_NAME` header in
+ `DataSpec.httpRequestHeaders`.
+ * Fail more explicitly when local file URIs contain invalid parts (e.g. a
+ fragment) ([#6470](https://github.com/google/ExoPlayer/issues/6470)).
+* DASH: Support negative @r values in segment timelines
+ ([#1787](https://github.com/google/ExoPlayer/issues/1787)).
+* HLS:
+ * Use peak bitrate rather than average bitrate for adaptive track
+ selection.
+ * Fix issue where streams could get stuck in an infinite buffering state
+ after a postroll ad
+ ([#6314](https://github.com/google/ExoPlayer/issues/6314)).
+* Matroska: Support lacing in Blocks
+ ([#3026](https://github.com/google/ExoPlayer/issues/3026)).
+* AV1 extension:
+ * New in this release. The AV1 extension allows use of the
+ [libgav1 software decoder](https://chromium.googlesource.com/codecs/libgav1/)
+ in ExoPlayer. You can read more about playing AV1 videos with ExoPlayer
+ [here](https://medium.com/google-exoplayer/playing-av1-videos-with-exoplayer-a7cb19bedef9).
+* VP9 extension:
+ * Update to use NDK r20.
+ * Rename `VpxVideoSurfaceView` to `VideoDecoderSurfaceView` and move it to
+ the core library.
+ * Move `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` to
+ `C.MSG_SET_OUTPUT_BUFFER_RENDERER`.
+ * Use `VideoDecoderRenderer` as an implementation of
+ `VideoDecoderOutputBufferRenderer`, instead of
+ `VideoDecoderSurfaceView`.
+* FLAC extension: Update to use NDK r20.
+* Opus extension: Update to use NDK r20.
+* FFmpeg extension:
+ * Update to use NDK r20.
+ * Update to use FFmpeg version 4.2. It is necessary to rebuild the native
+ part of the extension after this change, following the instructions in
+ the extension's readme.
+* MediaSession extension: Add `MediaSessionConnector.setCaptionCallback` to
+ support `ACTION_SET_CAPTIONING_ENABLED` events.
+* GVR extension: This extension is now deprecated.
+* Demo apps:
+ * Add
+ [SurfaceControl demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/surface)
+ to show how to use the Android 10 `SurfaceControl` API with ExoPlayer
+ ([#677](https://github.com/google/ExoPlayer/issues/677)).
+ * Add support for subtitle files to the
+ [Main demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/main)
+ ([#5523](https://github.com/google/ExoPlayer/issues/5523)).
+ * Remove the IMA demo app. IMA functionality is demonstrated by the
+ [main demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/main).
+ * Add basic DRM support to the
+ [Cast demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/cast).
+* TestUtils: Publish the `testutils` module to simplify unit testing with
+ ExoPlayer ([#6267](https://github.com/google/ExoPlayer/issues/6267)).
+* IMA extension: Remove `AdsManager` listeners on release to avoid leaking an
+ `AdEventListener` provided by the app
+ ([#6687](https://github.com/google/ExoPlayer/issues/6687)).
+
+### 2.10.8 (2019-11-19)
+
+* E-AC3 JOC
+ * Handle new signaling in DASH manifests
+ ([#6636](https://github.com/google/ExoPlayer/issues/6636)).
+ * Fix E-AC3 JOC passthrough playback failing to initialize due to
+ incorrect channel count check.
+* FLAC
+ * Fix sniffing for some FLAC streams.
+ * Fix FLAC `Format.bitrate` values.
+* Parse ALAC channel count and sample rate information from a more robust
+ source when contained in MP4
+ ([#6648](https://github.com/google/ExoPlayer/issues/6648)).
+* Fix seeking into multi-period content in the edge case that the period
+ containing the seek position has just been removed
+ ([#6641](https://github.com/google/ExoPlayer/issues/6641)).
+
+### 2.10.7 (2019-11-06)
+
+* HLS: Fix detection of Dolby Atmos to match the HLS authoring specification.
+* MediaSession extension: Update shuffle and repeat modes when playback state
+ is invalidated ([#6582](https://github.com/google/ExoPlayer/issues/6582)).
+* Fix the start of audio getting truncated when transitioning to a new item in
+ a playlist of Opus streams.
+
+### 2.10.6 (2019-10-17)
+
+* Add `Player.onPlaybackSuppressionReasonChanged` to allow listeners to detect
+ playbacks suppressions (e.g. transient audio focus loss) directly
+ ([#6203](https://github.com/google/ExoPlayer/issues/6203)).
+* DASH:
+ * Support `Label` elements
+ ([#6297](https://github.com/google/ExoPlayer/issues/6297)).
+ * Support legacy audio channel configuration
+ ([#6523](https://github.com/google/ExoPlayer/issues/6523)).
+* HLS: Add support for ID3 in EMSG when using FMP4 streams
+ ([spec](https://aomediacodec.github.io/av1-id3/)).
+* MP3: Add workaround to avoid prematurely ending playback of some SHOUTcast
+ live streams ([#6537](https://github.com/google/ExoPlayer/issues/6537),
+ [#6315](https://github.com/google/ExoPlayer/issues/6315) and
+ [#5658](https://github.com/google/ExoPlayer/issues/5658)).
+* Metadata: Expose the raw ICY metadata through `IcyInfo`
+ ([#6476](https://github.com/google/ExoPlayer/issues/6476)).
+* UI:
+ * Setting `app:played_color` on `PlayerView` and `PlayerControlView` no
+ longer adjusts the colors of the scrubber handle , buffered and unplayed
+ parts of the time bar. These can be set separately using
+ `app:scrubber_color`, `app:buffered_color` and `app_unplayed_color`
+ respectively.
+ * Setting `app:ad_marker_color` on `PlayerView` and `PlayerControlView` no
+ longer adjusts the color of played ad markers. The color of played ad
+ markers can be set separately using `app:played_ad_marker_color`.
+
+### 2.10.5 (2019-09-20)
+
+* Add `Player.isPlaying` and `EventListener.onIsPlayingChanged` to check
+ whether the playback position is advancing. This helps to determine if
+ playback is suppressed due to audio focus loss. Also add
+ `Player.getPlaybackSuppressedReason` to determine the reason of the
+ suppression ([#6203](https://github.com/google/ExoPlayer/issues/6203)).
+* Track selection
+ * Add `allowAudioMixedChannelCountAdaptiveness` parameter to
+ `DefaultTrackSelector` to allow adaptive selections of audio tracks with
+ different channel counts.
+ * Improve text selection logic to always prefer the better language
+ matches over other selection parameters.
+ * Fix audio selection issue where languages are compared by bitrate
+ ([#6335](https://github.com/google/ExoPlayer/issues/6335)).
+* Performance
+ * Increase maximum video buffer size from 13MB to 32MB. The previous
+ default was too small for high quality streams.
+ * Reset `DefaultBandwidthMeter` to initial values on network change.
+ * Bypass sniffing in `ProgressiveMediaPeriod` in case a single extractor
+ is provided ([#6325](https://github.com/google/ExoPlayer/issues/6325)).
+* Metadata
+ * Support EMSG V1 boxes in FMP4.
+ * Support unwrapping of nested metadata (e.g. ID3 and SCTE-35 in EMSG).
+* Add `HttpDataSource.getResponseCode` to provide the status code associated
+ with the most recent HTTP response.
+* Fix transitions between packed audio and non-packed audio segments in HLS
+ ([#6444](https://github.com/google/ExoPlayer/issues/6444)).
+* Fix issue where a request would be retried after encountering an error, even
+ though the `LoadErrorHandlingPolicy` classified the error as fatal.
+* Fix initialization data handling for FLAC in MP4
+ ([#6396](https://github.com/google/ExoPlayer/issues/6396),
+ [#6397](https://github.com/google/ExoPlayer/issues/6397)).
+* Fix decoder selection for E-AC3 JOC streams
+ ([#6398](https://github.com/google/ExoPlayer/issues/6398)).
+* Fix `PlayerNotificationManager` to show play icon rather than pause icon
+ when playback is ended
+ ([#6324](https://github.com/google/ExoPlayer/issues/6324)).
+* RTMP extension: Upgrade LibRtmp-Client-for-Android to fix RTMP playback
+ issues ([#4200](https://github.com/google/ExoPlayer/issues/4200),
+ [#4249](https://github.com/google/ExoPlayer/issues/4249),
+ [#4319](https://github.com/google/ExoPlayer/issues/4319),
+ [#4337](https://github.com/google/ExoPlayer/issues/4337)).
+* IMA extension: Fix crash in `ImaAdsLoader.onTimelineChanged`
+ ([#5831](https://github.com/google/ExoPlayer/issues/5831)).
+
+### 2.10.4 (2019-07-26)
+
+* Offline: Add `Scheduler` implementation that uses `WorkManager`.
+* Add ability to specify a description when creating notification channels via
+ ExoPlayer library classes.
+* Switch normalized BCP-47 language codes to use 2-letter ISO 639-1 language
+ tags instead of 3-letter ISO 639-2 language tags.
+* Ensure the `SilenceMediaSource` position is in range
+ ([#6229](https://github.com/google/ExoPlayer/issues/6229)).
+* WAV: Calculate correct duration for clipped streams
+ ([#6241](https://github.com/google/ExoPlayer/issues/6241)).
+* MP3: Use CBR header bitrate, not calculated bitrate. This reverts a change
+ from 2.9.3 ([#6238](https://github.com/google/ExoPlayer/issues/6238)).
+* FLAC extension: Parse `VORBIS_COMMENT` and `PICTURE` metadata
+ ([#5527](https://github.com/google/ExoPlayer/issues/5527)).
+* Fix issue where initial seek positions get ignored when playing a preroll ad
+ ([#6201](https://github.com/google/ExoPlayer/issues/6201)).
+* Fix issue where invalid language tags were normalized to "und" instead of
+ keeping the original
+ ([#6153](https://github.com/google/ExoPlayer/issues/6153)).
+* Fix `DataSchemeDataSource` re-opening and range requests
+ ([#6192](https://github.com/google/ExoPlayer/issues/6192)).
+* Fix FLAC and ALAC playback on some LG devices
+ ([#5938](https://github.com/google/ExoPlayer/issues/5938)).
+* Fix issue when calling `performClick` on `PlayerView` without
+ `PlayerControlView`
+ ([#6260](https://github.com/google/ExoPlayer/issues/6260)).
+* Fix issue where playback speeds are not used in adaptive track selections
+ after manual selection changes for other renderers
+ ([#6256](https://github.com/google/ExoPlayer/issues/6256)).
+
+### 2.10.3 (2019-07-09)
+
+* Display last frame when seeking to end of stream
+ ([#2568](https://github.com/google/ExoPlayer/issues/2568)).
+* Audio:
+ * Fix an issue where not all audio was played out when the configuration
+ for the underlying track was changing (e.g., at some period
+ transitions).
+ * Fix an issue where playback speed was applied inaccurately in playlists
+ ([#6117](https://github.com/google/ExoPlayer/issues/6117)).
+* UI: Fix `PlayerView` incorrectly consuming touch events if no controller is
+ attached ([#6109](https://github.com/google/ExoPlayer/issues/6109)).
+* CEA608: Fix repetition of special North American characters
+ ([#6133](https://github.com/google/ExoPlayer/issues/6133)).
+* FLV: Fix bug that caused playback of some live streams to not start
+ ([#6111](https://github.com/google/ExoPlayer/issues/6111)).
+* SmoothStreaming: Parse text stream `Subtype` into `Format.roleFlags`.
+* MediaSession extension: Fix `MediaSessionConnector.play()` not resuming
+ playback ([#6093](https://github.com/google/ExoPlayer/issues/6093)).
+
+### 2.10.2 (2019-06-03)
+
+* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
+ ([#5779](https://github.com/google/ExoPlayer/issues/5779)).
+* Add `SilenceMediaSource` that can be used to play silence of a given
+ duration ([#5735](https://github.com/google/ExoPlayer/issues/5735)).
+* Offline:
+ * Prevent unexpected `DownloadHelper.Callback.onPrepared` callbacks after
+ preparation of a `DownloadHelper` fails
+ ([#5915](https://github.com/google/ExoPlayer/issues/5915)).
+ * Fix `CacheUtil.cache()` downloading too much data
+ ([#5927](https://github.com/google/ExoPlayer/issues/5927)).
+ * Fix misreporting cached bytes when caching is paused
+ ([#5573](https://github.com/google/ExoPlayer/issues/5573)).
+* UI:
+ * Allow setting `DefaultTimeBar` attributes on `PlayerView` and
+ `PlayerControlView`.
+ * Change playback controls toggle from touch down to touch up events
+ ([#5784](https://github.com/google/ExoPlayer/issues/5784)).
+ * Fix issue where playback controls were not kept visible on key presses
+ ([#5963](https://github.com/google/ExoPlayer/issues/5963)).
+* Subtitles:
+ * CEA-608: Handle XDS and TEXT modes
+ ([#5807](https://github.com/google/ExoPlayer/pull/5807)).
+ * TTML: Fix bitmap rendering
+ ([#5633](https://github.com/google/ExoPlayer/pull/5633)).
+* IMA: Fix ad pod index offset calculation without preroll
+ ([#5928](https://github.com/google/ExoPlayer/issues/5928)).
+* Add a `playWhenReady` flag to MediaSessionConnector.PlaybackPreparer methods
+ to indicate whether a controller sent a play or only a prepare command. This
+ allows to take advantage of decoder reuse with the MediaSessionConnector
+ ([#5891](https://github.com/google/ExoPlayer/issues/5891)).
+* Add `ProgressUpdateListener` to `PlayerControlView`
+ ([#5834](https://github.com/google/ExoPlayer/issues/5834)).
+* Add support for auto-detecting UDP streams in `DefaultDataSource`
+ ([#6036](https://github.com/google/ExoPlayer/pull/6036)).
+* Allow enabling decoder fallback with `DefaultRenderersFactory`
+ ([#5942](https://github.com/google/ExoPlayer/issues/5942)).
+* Gracefully handle revoked `ACCESS_NETWORK_STATE` permission
+ ([#6019](https://github.com/google/ExoPlayer/issues/6019)).
+* Fix decoding problems when seeking back after seeking beyond a mid-roll ad
+ ([#6009](https://github.com/google/ExoPlayer/issues/6009)).
+* Fix application of `maxAudioBitrate` for adaptive audio track groups
+ ([#6006](https://github.com/google/ExoPlayer/issues/6006)).
+* Fix bug caused by parallel adaptive track selection using `Format`s without
+ bitrate information
+ ([#5971](https://github.com/google/ExoPlayer/issues/5971)).
+* Fix bug in `CastPlayer.getCurrentWindowIndex()`
+ ([#5955](https://github.com/google/ExoPlayer/issues/5955)).
+
+### 2.10.1 (2019-05-16)
+
+* Offline: Add option to remove all downloads.
+* HLS: Fix `NullPointerException` when using HLS chunkless preparation
+ ([#5868](https://github.com/google/ExoPlayer/issues/5868)).
+* Fix handling of empty values and line terminators in SHOUTcast ICY metadata
+ ([#5876](https://github.com/google/ExoPlayer/issues/5876)).
+* Fix DVB subtitles for SDK 28
+ ([#5862](https://github.com/google/ExoPlayer/issues/5862)).
+* Add a workaround for a decoder failure on ZTE Axon7 mini devices when
+ playing 48kHz audio
+ ([#5821](https://github.com/google/ExoPlayer/issues/5821)).
+
+### 2.10.0 (2019-04-15)
+
+* Core library:
+ * Improve decoder re-use between playbacks
+ ([#2826](https://github.com/google/ExoPlayer/issues/2826)). Read
+ [this blog post](https://medium.com/google-exoplayer/improved-decoder-reuse-in-exoplayer-ef4c6d99591d)
+ for more details.
+ * Rename `ExtractorMediaSource` to `ProgressiveMediaSource`.
+ * Fix issue where using `ProgressiveMediaSource.Factory` would mean that
+ `DefaultExtractorsFactory` would be kept by proguard. Custom
+ `ExtractorsFactory` instances must now be passed via the
+ `ProgressiveMediaSource.Factory` constructor, and `setExtractorsFactory`
+ is deprecated.
+ * Make the default minimum buffer size equal the maximum buffer size for
+ video playbacks
+ ([#2083](https://github.com/google/ExoPlayer/issues/2083)).
+ * Move `PriorityTaskManager` from `DefaultLoadControl` to
+ `SimpleExoPlayer`.
+ * Add new `ExoPlaybackException` types for remote exceptions and
+ out-of-memory errors.
+ * Use full BCP 47 language tags in `Format`.
+ * Do not retry failed loads whose error is `FileNotFoundException`.
+ * Fix issue where not resetting the position for a new `MediaSource` in
+ calls to `ExoPlayer.prepare` causes an `IndexOutOfBoundsException`
+ ([#5520](https://github.com/google/ExoPlayer/issues/5520)).
+* Offline:
+ * Improve offline support. `DownloadManager` now tracks all offline
+ content, not just tasks in progress. Read
+ [this page](https://exoplayer.dev/downloading-media.html) for more
+ details.
+* Caching:
+ * Improve performance of `SimpleCache`
+ ([#4253](https://github.com/google/ExoPlayer/issues/4253)).
+ * Cache data with unknown length by default. The previous flag to opt in
+ to this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
+ replaced with an opt out flag
+ (`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`).
+* Extractors:
+ * MP4/FMP4: Add support for Dolby Vision.
+ * MP4: Fix issue handling meta atoms in some streams
+ ([#5698](https://github.com/google/ExoPlayer/issues/5698),
+ [#5694](https://github.com/google/ExoPlayer/issues/5694)).
+ * MP3: Add support for SHOUTcast ICY metadata
+ ([#3735](https://github.com/google/ExoPlayer/issues/3735)).
+ * MP3: Fix ID3 frame unsychronization
+ ([#5673](https://github.com/google/ExoPlayer/issues/5673)).
+ * MP3: Fix playback of badly clipped files
+ ([#5772](https://github.com/google/ExoPlayer/issues/5772)).
+ * MPEG-TS: Enable HDMV DTS stream detection only if a flag is set. By
+ default (i.e. if the flag is not set), the 0x82 elementary stream type
+ is now treated as an SCTE subtitle track
+ ([#5330](https://github.com/google/ExoPlayer/issues/5330)).
+* Track selection:
+ * Add options for controlling audio track selections to
+ `DefaultTrackSelector`
+ ([#3314](https://github.com/google/ExoPlayer/issues/3314)).
+ * Update `TrackSelection.Factory` interface to support creating all track
+ selections together.
+ * Allow to specify a selection reason for a `SelectionOverride`.
+ * Select audio track based on system language if no preference is
+ provided.
+ * When no text language preference matches, only select forced text tracks
+ whose language matches the selected audio language.
+* UI:
+ * Update `DefaultTimeBar` based on duration of media and add parameter to
+ set the minimum update interval to control the smoothness of the updates
+ ([#5040](https://github.com/google/ExoPlayer/issues/5040)).
+ * Move creation of dialogs for `TrackSelectionView`s to
+ `TrackSelectionDialogBuilder` and add option to select multiple
+ overrides.
+ * Change signature of `PlayerNotificationManager.NotificationListener` to
+ better fit service requirements.
+ * Add option to include navigation actions in the compact mode of
+ notifications created using `PlayerNotificationManager`.
+ * Fix issues with flickering notifications on KitKat when using
+ `PlayerNotificationManager` and `DownloadNotificationUtil`. For the
+ latter, applications should switch to using
+ `DownloadNotificationHelper`.
+ * Fix accuracy of D-pad seeking in `DefaultTimeBar`
+ ([#5767](https://github.com/google/ExoPlayer/issues/5767)).
+* Audio:
+ * Allow `AudioProcessor`s to be drained of pending output after they are
+ reconfigured.
+ * Fix an issue that caused audio to be truncated at the end of a period
+ when switching to a new period where gapless playback information was
+ newly present or newly absent.
+ * Add support for reading AC-4 streams
+ ([#5303](https://github.com/google/ExoPlayer/pull/5303)).
+* Video:
+ * Remove `MediaCodecSelector.DEFAULT_WITH_FALLBACK`. Apps should instead
+ signal that fallback should be used by passing `true` as the
+ `enableDecoderFallback` parameter when instantiating the video renderer.
+ * Support video tunneling when the decoder is not listed first for the
+ MIME type ([#3100](https://github.com/google/ExoPlayer/issues/3100)).
+ * Query `MediaCodecList.ALL_CODECS` when selecting a tunneling decoder
+ ([#5547](https://github.com/google/ExoPlayer/issues/5547)).
+* DRM:
+ * Fix black flicker when keys rotate in DRM protected content
+ ([#3561](https://github.com/google/ExoPlayer/issues/3561)).
+ * Work around lack of LA_URL attribute in PlayReady key request init data.
+* CEA-608: Improved conformance to the specification
+ ([#3860](https://github.com/google/ExoPlayer/issues/3860)).
+* DASH:
+ * Parse role and accessibility descriptors into `Format.roleFlags`.
+ * Support multiple CEA-608 channels muxed into FMP4 representations
+ ([#5656](https://github.com/google/ExoPlayer/issues/5656)).
+* HLS:
+ * Prevent unnecessary reloads of initialization segments.
+ * Form an adaptive track group out of audio renditions with matching name.
+ * Support encrypted initialization segments
+ ([#5441](https://github.com/google/ExoPlayer/issues/5441)).
+ * Parse `EXT-X-MEDIA` `CHARACTERISTICS` attribute into `Format.roleFlags`.
+ * Add metadata entry for HLS tracks to expose master playlist information.
+ * Prevent `IndexOutOfBoundsException` in some live HLS scenarios
+ ([#5816](https://github.com/google/ExoPlayer/issues/5816)).
+* Support for playing spherical videos on Daydream.
+* Cast extension: Work around Cast framework returning a limited-size queue
+ items list ([#4964](https://github.com/google/ExoPlayer/issues/4964)).
+* VP9 extension: Remove RGB output mode and libyuv dependency, and switch to
+ surface YUV output as the default. Remove constructor parameters
+ `scaleToFit` and `useSurfaceYuvOutput`.
+* MediaSession extension:
+ * Let apps intercept media button events
+ ([#5179](https://github.com/google/ExoPlayer/issues/5179)).
+ * Fix issue with `TimelineQueueNavigator` not publishing the queue in
+ shuffled order when in shuffle mode.
+ * Allow handling of custom commands via `registerCustomCommandReceiver`.
+ * Add ability to include an extras `Bundle` when reporting a custom error.
+* Log warnings when extension native libraries can't be used, to help with
+ diagnosing playback failures
+ ([#5788](https://github.com/google/ExoPlayer/issues/5788)).
+
+### 2.9.6 (2019-02-19)
+
+* Remove `player` and `isTopLevelSource` parameters from
+ `MediaSource.prepare`.
+* IMA extension:
+ * Require setting the `Player` on `AdsLoader` instances before playback.
+ * Remove deprecated `ImaAdsMediaSource`. Create `AdsMediaSource` with an
+ `ImaAdsLoader` instead.
+ * Remove deprecated `AdsMediaSource` constructors. Listen for media source
+ events using `AdsMediaSource.addEventListener`, and ad interaction
+ events by adding a listener when building `ImaAdsLoader`.
+ * Allow apps to register playback-related obstructing views that are on
+ top of their ad display containers via `AdsLoader.AdViewProvider`.
+ `PlayerView` implements this interface and will register its control
+ view. This makes it possible for ad loading SDKs to calculate ad
+ viewability accurately.
+* DASH: Fix issue handling large `EventStream` presentation timestamps
+ ([#5490](https://github.com/google/ExoPlayer/issues/5490)).
+* HLS: Fix transition to STATE_ENDED when playing fragmented mp4 in chunkless
+ preparation ([#5524](https://github.com/google/ExoPlayer/issues/5524)).
+* Revert workaround for video quality problems with Amlogic decoders, as this
+ may cause problems for some devices and/or non-interlaced content
+ ([#5003](https://github.com/google/ExoPlayer/issues/5003)).
+
+### 2.9.5 (2019-01-31)
+
+* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
+* ConcatenatingMediaSource:
+ * Add `Handler` parameter to methods that take a callback `Runnable`.
+ * Fix issue with dropped messages when releasing the source
+ ([#5464](https://github.com/google/ExoPlayer/issues/5464)).
+* ExtractorMediaSource: Fix issue that could cause the player to get stuck
+ buffering at the end of the media.
+* PlayerView: Fix issue preventing `OnClickListener` from receiving events
+ ([#5433](https://github.com/google/ExoPlayer/issues/5433)).
+* IMA extension: Upgrade IMA dependency to 3.10.6.
+* Cronet extension: Upgrade Cronet dependency to 71.3578.98.
+* OkHttp extension: Upgrade OkHttp dependency to 3.12.1.
+* MP3: Wider fix for issue where streams would play twice on some Samsung
+ devices ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
+
+### 2.9.4 (2019-01-15)
+
+* IMA extension: Clear ads loader listeners on release
+ ([#4114](https://github.com/google/ExoPlayer/issues/4114)).
+* SmoothStreaming: Fix support for subtitles in DRM protected streams
+ ([#5378](https://github.com/google/ExoPlayer/issues/5378)).
+* FFmpeg extension: Treat invalid data errors as non-fatal to match the
+ behavior of MediaCodec
+ ([#5293](https://github.com/google/ExoPlayer/issues/5293)).
+* GVR extension: upgrade GVR SDK dependency to 1.190.0.
+* Associate fatal player errors of type SOURCE with the loading source in
+ `AnalyticsListener.EventTime`
([#5407](https://github.com/google/ExoPlayer/issues/5407)).
- * Add `Format` and renderer support flags to renderer `ExoPlaybackException`s.
- * Where there are multiple platform decoders for a given MIME type, prefer to
- use one that advertises support for the profile and level of the media being
- played over one that does not, even if it does not come first in the
- `MediaCodecList`.
-* DRM:
- * Inject `DrmSessionManager` into the `MediaSources` instead of `Renderers`.
- This allows each `MediaSource` in a `ConcatenatingMediaSource` to use a
- different `DrmSessionManager`
- ([#5619](https://github.com/google/ExoPlayer/issues/5619)).
- * Add `DefaultDrmSessionManager.Builder`, and remove
- `DefaultDrmSessionManager` static factory methods that leaked
- `ExoMediaDrm` instances
- ([#4721](https://github.com/google/ExoPlayer/issues/4721)).
- * Add support for the use of secure decoders when playing clear content
- ([#4867](https://github.com/google/ExoPlayer/issues/4867)). This can
- be enabled using `DefaultDrmSessionManager.Builder`'s
- `setUseDrmSessionsForClearContent` method.
- * Add support for custom `LoadErrorHandlingPolicies` in key and provisioning
- requests ([#6334](https://github.com/google/ExoPlayer/issues/6334)). Custom
- policies can be passed via `DefaultDrmSessionManager.Builder`'s
- `setLoadErrorHandlingPolicy` method.
- * Use `ExoMediaDrm.Provider` in `OfflineLicenseHelper` to avoid leaking
- `ExoMediaDrm` instances
- ([#4721](https://github.com/google/ExoPlayer/issues/4721)).
-* Track selection:
- * Update `DefaultTrackSelector` to set a viewport constraint for the default
- display by default.
- * Update `DefaultTrackSelector` to set text language and role flag
- constraints for the device's accessibility settings by default
- ([#5749](https://github.com/google/ExoPlayer/issues/5749)).
- * Add option to set preferred text role flags using
- `DefaultTrackSelector.ParametersBuilder.setPreferredTextRoleFlags`.
-* Android 10:
- * Set `compileSdkVersion` to 29 to enable use of Android 10 APIs.
- * Expose new `isHardwareAccelerated`, `isSoftwareOnly` and `isVendor` flags
- in `MediaCodecInfo`
- ([#5839](https://github.com/google/ExoPlayer/issues/5839)).
- * Add `allowedCapturePolicy` field to `AudioAttributes` to allow to
- configuration of the audio capture policy.
-* Video:
- * Pass the codec output `MediaFormat` to `VideoFrameMetadataListener`.
- * Fix byte order of HDR10+ static metadata to match CTA-861.3.
- * Support out-of-band HDR10+ dynamic metadata for VP9 in WebM/Matroska.
- * Assume that protected content requires a secure decoder when evaluating
- whether `MediaCodecVideoRenderer` supports a given video format
- ([#5568](https://github.com/google/ExoPlayer/issues/5568)).
- * Fix Dolby Vision fallback to AVC and HEVC.
- * Fix early end-of-stream detection when using video tunneling, on API level
- 23 and above.
- * Fix an issue where a keyframe was rendered rather than skipped when
- performing an exact seek to a non-zero position close to the start of the
- stream.
-* Audio:
- * Fix the start of audio getting truncated when transitioning to a new
- item in a playlist of Opus streams.
- * Workaround broken raw audio decoding on Oppo R9
- ([#5782](https://github.com/google/ExoPlayer/issues/5782)).
- * Reconfigure audio sink when PCM encoding changes
- ([#6601](https://github.com/google/ExoPlayer/issues/6601)).
- * Allow `AdtsExtractor` to encounter EOF when calculating average frame size
- ([#6700](https://github.com/google/ExoPlayer/issues/6700)).
-* Text:
- * Add support for position and overlapping start/end times in SSA/ASS
- subtitles ([#6320](https://github.com/google/ExoPlayer/issues/6320)).
- * Require an end time or duration for SubRip (SRT) and SubStation Alpha
- (SSA/ASS) subtitles. This applies to both sidecar files & subtitles
- [embedded in Matroska streams](https://matroska.org/technical/specs/subtitles/index.html).
-* UI:
- * Make showing and hiding player controls accessible to TalkBack in
- `PlayerView`.
- * Rename `spherical_view` surface type to `spherical_gl_surface_view`.
- * Make it easier to override the shuffle, repeat, fullscreen, VR and small
- notification icon assets
- ([#6709](https://github.com/google/ExoPlayer/issues/6709)).
-* Analytics:
- * Remove `AnalyticsCollector.Factory`. Instances should be created directly,
- and the `Player` should be set by calling `AnalyticsCollector.setPlayer`.
- * Add `PlaybackStatsListener` to collect `PlaybackStats` for analysis and
- analytics reporting.
-* DataSource
- * Add `DataSpec.httpRequestHeaders` to support setting per-request headers for
- HTTP and HTTPS.
- * Remove the `DataSpec.FLAG_ALLOW_ICY_METADATA` flag. Use is replaced by
- setting the `IcyHeaders.REQUEST_HEADER_ENABLE_METADATA_NAME` header in
- `DataSpec.httpRequestHeaders`.
- * Fail more explicitly when local file URIs contain invalid parts (e.g. a
- fragment) ([#6470](https://github.com/google/ExoPlayer/issues/6470)).
-* DASH: Support negative @r values in segment timelines
- ([#1787](https://github.com/google/ExoPlayer/issues/1787)).
-* HLS:
- * Use peak bitrate rather than average bitrate for adaptive track selection.
- * Fix issue where streams could get stuck in an infinite buffering state
- after a postroll ad
- ([#6314](https://github.com/google/ExoPlayer/issues/6314)).
-* Matroska: Support lacing in Blocks
- ([#3026](https://github.com/google/ExoPlayer/issues/3026)).
-* AV1 extension:
- * New in this release. The AV1 extension allows use of the
- [libgav1 software decoder](https://chromium.googlesource.com/codecs/libgav1/)
- in ExoPlayer. You can read more about playing AV1 videos with ExoPlayer
- [here](https://medium.com/google-exoplayer/playing-av1-videos-with-exoplayer-a7cb19bedef9).
-* VP9 extension:
- * Update to use NDK r20.
- * Rename `VpxVideoSurfaceView` to `VideoDecoderSurfaceView` and move it to the
- core library.
- * Move `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` to
- `C.MSG_SET_OUTPUT_BUFFER_RENDERER`.
- * Use `VideoDecoderRenderer` as an implementation of
- `VideoDecoderOutputBufferRenderer`, instead of `VideoDecoderSurfaceView`.
-* FLAC extension: Update to use NDK r20.
-* Opus extension: Update to use NDK r20.
-* FFmpeg extension:
- * Update to use NDK r20.
- * Update to use FFmpeg version 4.2. It is necessary to rebuild the native part
- of the extension after this change, following the instructions in the
- extension's readme.
-* MediaSession extension: Add `MediaSessionConnector.setCaptionCallback` to
- support `ACTION_SET_CAPTIONING_ENABLED` events.
-* GVR extension: This extension is now deprecated.
-* Demo apps:
- * Add [SurfaceControl demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/surface)
- to show how to use the Android 10 `SurfaceControl` API with ExoPlayer
- ([#677](https://github.com/google/ExoPlayer/issues/677)).
- * Add support for subtitle files to the
- [Main demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/main)
- ([#5523](https://github.com/google/ExoPlayer/issues/5523)).
- * Remove the IMA demo app. IMA functionality is demonstrated by the
- [main demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/main).
- * Add basic DRM support to the
- [Cast demo app](https://github.com/google/ExoPlayer/tree/r2.11.0/demos/cast).
-* TestUtils: Publish the `testutils` module to simplify unit testing with
- ExoPlayer ([#6267](https://github.com/google/ExoPlayer/issues/6267)).
-* IMA extension: Remove `AdsManager` listeners on release to avoid leaking an
- `AdEventListener` provided by the app
- ([#6687](https://github.com/google/ExoPlayer/issues/6687)).
+* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue
+ where using lazy preparation in `ConcatenatingMediaSource` with an
+ `ExtractorMediaSource` overrides initial seek positions
+ ([#5350](https://github.com/google/ExoPlayer/issues/5350)).
+* Add subtext to the `MediaDescriptionAdapter` of the
+ `PlayerNotificationManager`.
+* Add workaround for video quality problems with Amlogic decoders
+ ([#5003](https://github.com/google/ExoPlayer/issues/5003)).
+* Fix issue where sending callbacks for playlist changes may cause problems
+ because of parallel player access
+ ([#5240](https://github.com/google/ExoPlayer/issues/5240)).
+* Fix issue with reusing a `ClippingMediaSource` with an inner
+ `ExtractorMediaSource` and a non-zero start position
+ ([#5351](https://github.com/google/ExoPlayer/issues/5351)).
+* Fix issue where uneven track durations in MP4 streams can cause OOM problems
+ ([#3670](https://github.com/google/ExoPlayer/issues/3670)).
-### 2.10.8 (2019-11-19) ###
+### 2.9.3 (2018-12-20)
-* E-AC3 JOC
- * Handle new signaling in DASH manifests
- ([#6636](https://github.com/google/ExoPlayer/issues/6636)).
- * Fix E-AC3 JOC passthrough playback failing to initialize due to incorrect
- channel count check.
-* FLAC
- * Fix sniffing for some FLAC streams.
- * Fix FLAC `Format.bitrate` values.
-* Parse ALAC channel count and sample rate information from a more robust source
- when contained in MP4
- ([#6648](https://github.com/google/ExoPlayer/issues/6648)).
-* Fix seeking into multi-period content in the edge case that the period
- containing the seek position has just been removed
- ([#6641](https://github.com/google/ExoPlayer/issues/6641)).
+* Captions: Support PNG subtitles in SMPTE-TT
+ ([#1583](https://github.com/google/ExoPlayer/issues/1583)).
+* MPEG-TS: Use random access indicators to minimize the need for
+ `FLAG_ALLOW_NON_IDR_KEYFRAMES`.
+* Downloading: Reduce time taken to remove downloads
+ ([#5136](https://github.com/google/ExoPlayer/issues/5136)).
+* MP3:
+ * Use the true bitrate for constant-bitrate MP3 seeking.
+ * Fix issue where streams would play twice on some Samsung devices
+ ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
+* Fix regression where some audio formats were incorrectly marked as being
+ unplayable due to under-reporting of platform decoder capabilities
+ ([#5145](https://github.com/google/ExoPlayer/issues/5145)).
+* Fix decode-only frame skipping on Nvidia Shield TV devices.
+* Workaround for MiTV (dangal) issue when swapping output surface
+ ([#5169](https://github.com/google/ExoPlayer/issues/5169)).
-### 2.10.7 (2019-11-06) ###
+### 2.9.2 (2018-11-28)
-* HLS: Fix detection of Dolby Atmos to match the HLS authoring specification.
-* MediaSession extension: Update shuffle and repeat modes when playback state
- is invalidated ([#6582](https://github.com/google/ExoPlayer/issues/6582)).
-* Fix the start of audio getting truncated when transitioning to a new
- item in a playlist of Opus streams.
+* HLS:
+ * Fix issue causing unnecessary media playlist requests when playing live
+ streams ([#5059](https://github.com/google/ExoPlayer/issues/5059)).
+ * Fix decoder re-instantiation issue for packed audio streams
+ ([#5063](https://github.com/google/ExoPlayer/issues/5063)).
+* MP4: Support Opus and FLAC in the MP4 container, and in DASH
+ ([#4883](https://github.com/google/ExoPlayer/issues/4883)).
+* DASH: Fix detecting the end of live events
+ ([#4780](https://github.com/google/ExoPlayer/issues/4780)).
+* Spherical video: Fall back to `TYPE_ROTATION_VECTOR` if
+ `TYPE_GAME_ROTATION_VECTOR` is unavailable
+ ([#5119](https://github.com/google/ExoPlayer/issues/5119)).
+* Support seeking for a wider range of MPEG-TS streams
+ ([#5097](https://github.com/google/ExoPlayer/issues/5097)).
+* Include channel count in audio capabilities check
+ ([#4690](https://github.com/google/ExoPlayer/issues/4690)).
+* Fix issue with applying the `show_buffering` attribute in `PlayerView`
+ ([#5139](https://github.com/google/ExoPlayer/issues/5139)).
+* Fix issue where null `Metadata` was output when it failed to decode
+ ([#5149](https://github.com/google/ExoPlayer/issues/5149)).
+* Fix playback of some invalid but playable MP4 streams by replacing
+ assertions with logged warnings in sample table parsing code
+ ([#5162](https://github.com/google/ExoPlayer/issues/5162)).
+* Fix UUID passed to `MediaCrypto` when using `C.CLEARKEY_UUID` before API 27.
-### 2.10.6 (2019-10-17) ###
+### 2.9.1 (2018-11-01)
-* Add `Player.onPlaybackSuppressionReasonChanged` to allow listeners to
- detect playbacks suppressions (e.g. transient audio focus loss) directly
- ([#6203](https://github.com/google/ExoPlayer/issues/6203)).
-* DASH:
- * Support `Label` elements
- ([#6297](https://github.com/google/ExoPlayer/issues/6297)).
- * Support legacy audio channel configuration
- ([#6523](https://github.com/google/ExoPlayer/issues/6523)).
-* HLS: Add support for ID3 in EMSG when using FMP4 streams
- ([spec](https://aomediacodec.github.io/av1-id3/)).
-* MP3: Add workaround to avoid prematurely ending playback of some SHOUTcast
- live streams ([#6537](https://github.com/google/ExoPlayer/issues/6537),
- [#6315](https://github.com/google/ExoPlayer/issues/6315) and
- [#5658](https://github.com/google/ExoPlayer/issues/5658)).
-* Metadata: Expose the raw ICY metadata through `IcyInfo`
- ([#6476](https://github.com/google/ExoPlayer/issues/6476)).
-* UI:
- * Setting `app:played_color` on `PlayerView` and `PlayerControlView` no longer
- adjusts the colors of the scrubber handle , buffered and unplayed parts of
- the time bar. These can be set separately using `app:scrubber_color`,
- `app:buffered_color` and `app_unplayed_color` respectively.
- * Setting `app:ad_marker_color` on `PlayerView` and `PlayerControlView` no
- longer adjusts the color of played ad markers. The color of played ad
- markers can be set separately using `app:played_ad_marker_color`.
+* Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext`
+ and `Player.hasPrevious`
+ ([#4863](https://github.com/google/ExoPlayer/issues/4863)).
+* Improve initial bandwidth meter estimates using the current country and
+ network type.
+* IMA extension:
+ * For preroll to live stream transitions, project forward the loading
+ position to avoid being behind the live window.
+ * Let apps specify whether to focus the skip button on ATV
+ ([#5019](https://github.com/google/ExoPlayer/issues/5019)).
+* MP3:
+ * Support seeking based on MLLT metadata
+ ([#3241](https://github.com/google/ExoPlayer/issues/3241)).
+ * Fix handling of streams with appended data
+ ([#4954](https://github.com/google/ExoPlayer/issues/4954)).
+* DASH: Parse ProgramInformation element if present in the manifest.
+* HLS:
+ * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload
+ reader factory flags
+ ([#4861](https://github.com/google/ExoPlayer/issues/4861)).
+ * Fix bug in segment sniffing
+ ([#5039](https://github.com/google/ExoPlayer/issues/5039)).
+* SubRip: Add support for alignment tags, and remove tags from the displayed
+ captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
+* Fix issue with blind seeking to windows with non-zero offset in a
+ `ConcatenatingMediaSource`
+ ([#4873](https://github.com/google/ExoPlayer/issues/4873)).
+* Fix logic for enabling next and previous actions in `TimelineQueueNavigator`
+ ([#5065](https://github.com/google/ExoPlayer/issues/5065)).
+* Fix issue where audio focus handling could not be disabled after enabling it
+ ([#5055](https://github.com/google/ExoPlayer/issues/5055)).
+* Fix issue where subtitles were positioned incorrectly if `SubtitleView` had
+ a non-zero position offset to its parent
+ ([#4788](https://github.com/google/ExoPlayer/issues/4788)).
+* Fix issue where the buffered position was not updated correctly when
+ transitioning between periods
+ ([#4899](https://github.com/google/ExoPlayer/issues/4899)).
+* Fix issue where a `NullPointerException` is thrown when removing an
+ unprepared media source from a `ConcatenatingMediaSource` with the
+ `useLazyPreparation` option enabled
+ ([#4986](https://github.com/google/ExoPlayer/issues/4986)).
+* Work around an issue where a non-empty end-of-stream audio buffer would be
+ output with timestamp zero, causing the player position to jump backwards
+ ([#5045](https://github.com/google/ExoPlayer/issues/5045)).
+* Suppress a spurious assertion failure on some Samsung devices
+ ([#4532](https://github.com/google/ExoPlayer/issues/4532)).
+* Suppress spurious "references unknown class member" shrinking warning
+ ([#4890](https://github.com/google/ExoPlayer/issues/4890)).
+* Swap recommended order for google() and jcenter() in gradle config
+ ([#4997](https://github.com/google/ExoPlayer/issues/4997)).
-### 2.10.5 (2019-09-20) ###
+### 2.9.0 (2018-09-06)
-* Add `Player.isPlaying` and `EventListener.onIsPlayingChanged` to check whether
- the playback position is advancing. This helps to determine if playback is
- suppressed due to audio focus loss. Also add
- `Player.getPlaybackSuppressedReason` to determine the reason of the
- suppression ([#6203](https://github.com/google/ExoPlayer/issues/6203)).
-* Track selection
- * Add `allowAudioMixedChannelCountAdaptiveness` parameter to
- `DefaultTrackSelector` to allow adaptive selections of audio tracks with
- different channel counts.
- * Improve text selection logic to always prefer the better language matches
- over other selection parameters.
- * Fix audio selection issue where languages are compared by bitrate
- ([#6335](https://github.com/google/ExoPlayer/issues/6335)).
-* Performance
- * Increase maximum video buffer size from 13MB to 32MB. The previous default
- was too small for high quality streams.
- * Reset `DefaultBandwidthMeter` to initial values on network change.
- * Bypass sniffing in `ProgressiveMediaPeriod` in case a single extractor is
- provided ([#6325](https://github.com/google/ExoPlayer/issues/6325)).
-* Metadata
- * Support EMSG V1 boxes in FMP4.
- * Support unwrapping of nested metadata (e.g. ID3 and SCTE-35 in EMSG).
-* Add `HttpDataSource.getResponseCode` to provide the status code associated
- with the most recent HTTP response.
-* Fix transitions between packed audio and non-packed audio segments in HLS
- ([#6444](https://github.com/google/ExoPlayer/issues/6444)).
-* Fix issue where a request would be retried after encountering an error, even
- though the `LoadErrorHandlingPolicy` classified the error as fatal.
-* Fix initialization data handling for FLAC in MP4
- ([#6396](https://github.com/google/ExoPlayer/issues/6396),
- [#6397](https://github.com/google/ExoPlayer/issues/6397)).
-* Fix decoder selection for E-AC3 JOC streams
- ([#6398](https://github.com/google/ExoPlayer/issues/6398)).
-* Fix `PlayerNotificationManager` to show play icon rather than pause icon when
- playback is ended ([#6324](https://github.com/google/ExoPlayer/issues/6324)).
-* RTMP extension: Upgrade LibRtmp-Client-for-Android to fix RTMP playback issues
- ([#4200](https://github.com/google/ExoPlayer/issues/4200),
- [#4249](https://github.com/google/ExoPlayer/issues/4249),
- [#4319](https://github.com/google/ExoPlayer/issues/4319),
- [#4337](https://github.com/google/ExoPlayer/issues/4337)).
-* IMA extension: Fix crash in `ImaAdsLoader.onTimelineChanged`
- ([#5831](https://github.com/google/ExoPlayer/issues/5831)).
+* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to
+ add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to
+ their gradle settings to ensure bytecode compatibility.
+* Set `compileSdkVersion` and `targetSdkVersion` to 28.
+* Support for automatic audio focus handling via
+ `SimpleExoPlayer.setAudioAttributes`.
+* Add `ExoPlayer.retry` convenience method.
+* Add `AudioListener` for listening to changes in audio configuration during
+ playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)).
+* Add `LoadErrorHandlingPolicy` to allow configuration of load error handling
+ across `MediaSource` implementations
+ ([#3370](https://github.com/google/ExoPlayer/issues/3370)).
+* Allow passing a `Looper`, which specifies the thread that must be used to
+ access the player, when instantiating player instances using
+ `ExoPlayerFactory`
+ ([#4278](https://github.com/google/ExoPlayer/issues/4278)).
+* Allow setting log level for ExoPlayer logcat output
+ ([#4665](https://github.com/google/ExoPlayer/issues/4665)).
+* Simplify `BandwidthMeter` injection: The `BandwidthMeter` should now be
+ passed directly to `ExoPlayerFactory`, instead of to
+ `TrackSelection.Factory` and `DataSource.Factory`. The `BandwidthMeter` is
+ passed to the components that need it internally. The `BandwidthMeter` may
+ also be omitted, in which case a default instance will be used.
+* Spherical video:
+ * Support for spherical video by setting `surface_type="spherical_view"`
+ on `PlayerView`.
+ * Support for
+ [VR180](https://github.com/google/spatial-media/blob/master/docs/vr180.md).
+* HLS:
+ * Support PlayReady.
+ * Add container format sniffing
+ ([#2025](https://github.com/google/ExoPlayer/issues/2025)).
+ * Support alternative `EXT-X-KEY` tags.
+ * Support `EXT-X-INDEPENDENT-SEGMENTS` in the master playlist.
+ * Support variable substitution
+ ([#4422](https://github.com/google/ExoPlayer/issues/4422)).
+ * Fix the bitrate being unset on primary track sample formats
+ ([#3297](https://github.com/google/ExoPlayer/issues/3297)).
+ * Make `HlsMediaSource.Factory` take a factory of trackers instead of a
+ tracker instance
+ ([#4814](https://github.com/google/ExoPlayer/issues/4814)).
+* DASH:
+ * Support `messageData` attribute for in-manifest event streams.
+ * Clip periods to their specified durations
+ ([#4185](https://github.com/google/ExoPlayer/issues/4185)).
+* Improve seeking support for progressive streams:
+ * Support seeking in MPEG-TS
+ ([#966](https://github.com/google/ExoPlayer/issues/966)).
+ * Support seeking in MPEG-PS
+ ([#4476](https://github.com/google/ExoPlayer/issues/4476)).
+ * Support approximate seeking in ADTS using a constant bitrate assumption
+ ([#4548](https://github.com/google/ExoPlayer/issues/4548)). The
+ `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` flag must be set on the extractor
+ to enable this functionality.
+ * Support approximate seeking in AMR using a constant bitrate assumption.
+ The `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` flag must be set on the
+ extractor to enable this functionality.
+ * Add `DefaultExtractorsFactory.setConstantBitrateSeekingEnabled` to
+ enable approximate seeking using a constant bitrate assumption on all
+ extractors that support it.
+* Video:
+ * Add callback to `VideoListener` to notify of surface size changes.
+ * Improve performance when playing high frame-rate content, and when
+ playing at greater than 1x speed
+ ([#2777](https://github.com/google/ExoPlayer/issues/2777)).
+ * Scale up the initial video decoder maximum input size so playlist
+ transitions with small increases in maximum sample size do not require
+ reinitialization
+ ([#4510](https://github.com/google/ExoPlayer/issues/4510)).
+ * Fix a bug where the player would not transition to the ended state when
+ playing video in tunneled mode.
+* Audio:
+ * Support attaching auxiliary audio effects to the `AudioTrack` via
+ `Player.setAuxEffectInfo` and `Player.clearAuxEffectInfo`.
+ * Support seamless adaptation while playing xHE-AAC streams.
+ ([#4360](https://github.com/google/ExoPlayer/issues/4360)).
+ * Increase `AudioTrack` buffer sizes to the theoretical maximum required
+ for each encoding for passthrough playbacks
+ ([#3803](https://github.com/google/ExoPlayer/issues/3803)).
+ * WAV: Fix issue where white noise would be output at the end of playback
+ ([#4724](https://github.com/google/ExoPlayer/issues/4724)).
+ * MP3: Fix issue where streams would play twice on the SM-T530
+ ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
+* Analytics:
+ * Add callbacks to `DefaultDrmSessionEventListener` and
+ `AnalyticsListener` to be notified of acquired and released DRM
+ sessions.
+ * Add uri field to `LoadEventInfo` in `MediaSourceEventListener` and
+ `AnalyticsListener` callbacks. This uri is the redirected uri if
+ redirection occurred
+ ([#2054](https://github.com/google/ExoPlayer/issues/2054)).
+ * Add response headers field to `LoadEventInfo` in
+ `MediaSourceEventListener` and `AnalyticsListener` callbacks
+ ([#4361](https://github.com/google/ExoPlayer/issues/4361) and
+ [#4615](https://github.com/google/ExoPlayer/issues/4615)).
+* UI:
+ * Add option to `PlayerView` to show buffering view when playWhenReady is
+ false ([#4304](https://github.com/google/ExoPlayer/issues/4304)).
+ * Allow any `Drawable` to be used as `PlayerView` default artwork.
+* ConcatenatingMediaSource:
+ * Support lazy preparation of playlist media sources
+ ([#3972](https://github.com/google/ExoPlayer/issues/3972)).
+ * Support range removal with `removeMediaSourceRange` methods
+ ([#4542](https://github.com/google/ExoPlayer/issues/4542)).
+ * Support setting a new shuffle order with `setShuffleOrder`
+ ([#4791](https://github.com/google/ExoPlayer/issues/4791)).
+* MPEG-TS: Support CEA-608/708 in H262
+ ([#2565](https://github.com/google/ExoPlayer/issues/2565)).
+* Allow configuration of the back buffer in `DefaultLoadControl.Builder`
+ ([#4857](https://github.com/google/ExoPlayer/issues/4857)).
+* Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when
+ creating a `CacheDataSource`.
+* Provide additional information for adaptive track selection.
+ `TrackSelection.updateSelectedTrack` has two new parameters for the current
+ queue of media chunks and iterators for information about upcoming chunks.
+* Allow `MediaCodecSelector`s to return multiple compatible decoders for
+ `MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that
+ falls back to less preferred decoders like `MediaCodec.createDecoderByType`
+ ([#273](https://github.com/google/ExoPlayer/issues/273)).
+* Enable gzip for requests made by `SingleSampleMediaSource`
+ ([#4771](https://github.com/google/ExoPlayer/issues/4771)).
+* Fix bug reporting buffered position for multi-period windows, and add
+ convenience methods `Player.getTotalBufferedDuration` and
+ `Player.getContentBufferedDuration`
+ ([#4023](https://github.com/google/ExoPlayer/issues/4023)).
+* Fix bug where transitions to clipped media sources would happen too early
+ ([#4583](https://github.com/google/ExoPlayer/issues/4583)).
+* Fix bugs reporting events for multi-period media sources
+ ([#4492](https://github.com/google/ExoPlayer/issues/4492) and
+ [#4634](https://github.com/google/ExoPlayer/issues/4634)).
+* Fix issue where removing looping media from a playlist throws an exception
+ ([#4871](https://github.com/google/ExoPlayer/issues/4871).
+* Fix issue where the preferred audio or text track would not be selected if
+ mapped onto a secondary renderer of the corresponding type
+ ([#4711](http://github.com/google/ExoPlayer/issues/4711)).
+* Fix issue where errors of upcoming playlist items are thrown too early
+ ([#4661](https://github.com/google/ExoPlayer/issues/4661)).
+* Allow edit lists which do not start with a sync sample.
+ ([#4774](https://github.com/google/ExoPlayer/issues/4774)).
+* Fix issue with audio discontinuities at period transitions, e.g. when
+ looping ([#3829](https://github.com/google/ExoPlayer/issues/3829)).
+* Fix issue where `player.getCurrentTag()` throws an
+ `IndexOutOfBoundsException`
+ ([#4822](https://github.com/google/ExoPlayer/issues/4822)).
+* Fix bug preventing use of multiple key session support (`multiSession=true`)
+ for non-Widevine `DefaultDrmSessionManager` instances
+ ([#4834](https://github.com/google/ExoPlayer/issues/4834)).
+* Fix issue where audio and video would desynchronize when playing
+ concatenations of gapless content
+ ([#4559](https://github.com/google/ExoPlayer/issues/4559)).
+* IMA extension:
+ * Refine the previous fix for empty ad groups to avoid discarding ad
+ breaks unnecessarily
+ ([#4030](https://github.com/google/ExoPlayer/issues/4030) and
+ [#4280](https://github.com/google/ExoPlayer/issues/4280)).
+ * Fix handling of empty postrolls
+ ([#4681](https://github.com/google/ExoPlayer/issues/4681)).
+ * Fix handling of postrolls with multiple ads
+ ([#4710](https://github.com/google/ExoPlayer/issues/4710)).
+* MediaSession extension:
+ * Add `MediaSessionConnector.setCustomErrorMessage` to support setting
+ custom error messages.
+ * Add `MediaMetadataProvider` to support setting custom metadata
+ ([#3497](https://github.com/google/ExoPlayer/issues/3497)).
+* Cronet extension: Now distributed via jCenter.
+* FFmpeg extension: Support mu-law and A-law PCM.
-### 2.10.4 (2019-07-26) ###
+### 2.8.4 (2018-08-17)
-* Offline: Add `Scheduler` implementation that uses `WorkManager`.
-* Add ability to specify a description when creating notification channels via
- ExoPlayer library classes.
-* Switch normalized BCP-47 language codes to use 2-letter ISO 639-1 language
- tags instead of 3-letter ISO 639-2 language tags.
-* Ensure the `SilenceMediaSource` position is in range
- ([#6229](https://github.com/google/ExoPlayer/issues/6229)).
-* WAV: Calculate correct duration for clipped streams
- ([#6241](https://github.com/google/ExoPlayer/issues/6241)).
-* MP3: Use CBR header bitrate, not calculated bitrate. This reverts a change
- from 2.9.3 ([#6238](https://github.com/google/ExoPlayer/issues/6238)).
-* FLAC extension: Parse `VORBIS_COMMENT` and `PICTURE` metadata
- ([#5527](https://github.com/google/ExoPlayer/issues/5527)).
-* Fix issue where initial seek positions get ignored when playing a preroll ad
- ([#6201](https://github.com/google/ExoPlayer/issues/6201)).
-* Fix issue where invalid language tags were normalized to "und" instead of
- keeping the original
- ([#6153](https://github.com/google/ExoPlayer/issues/6153)).
-* Fix `DataSchemeDataSource` re-opening and range requests
- ([#6192](https://github.com/google/ExoPlayer/issues/6192)).
-* Fix FLAC and ALAC playback on some LG devices
- ([#5938](https://github.com/google/ExoPlayer/issues/5938)).
-* Fix issue when calling `performClick` on `PlayerView` without
- `PlayerControlView`
- ([#6260](https://github.com/google/ExoPlayer/issues/6260)).
-* Fix issue where playback speeds are not used in adaptive track selections
- after manual selection changes for other renderers
- ([#6256](https://github.com/google/ExoPlayer/issues/6256)).
+* IMA extension: Improve handling of consecutive empty ad groups
+ ([#4030](https://github.com/google/ExoPlayer/issues/4030)),
+ ([#4280](https://github.com/google/ExoPlayer/issues/4280)).
-### 2.10.3 (2019-07-09) ###
+### 2.8.3 (2018-07-23)
-* Display last frame when seeking to end of stream
- ([#2568](https://github.com/google/ExoPlayer/issues/2568)).
-* Audio:
- * Fix an issue where not all audio was played out when the configuration
- for the underlying track was changing (e.g., at some period transitions).
- * Fix an issue where playback speed was applied inaccurately in playlists
- ([#6117](https://github.com/google/ExoPlayer/issues/6117)).
-* UI: Fix `PlayerView` incorrectly consuming touch events if no controller is
- attached ([#6109](https://github.com/google/ExoPlayer/issues/6109)).
-* CEA608: Fix repetition of special North American characters
- ([#6133](https://github.com/google/ExoPlayer/issues/6133)).
-* FLV: Fix bug that caused playback of some live streams to not start
- ([#6111](https://github.com/google/ExoPlayer/issues/6111)).
-* SmoothStreaming: Parse text stream `Subtype` into `Format.roleFlags`.
-* MediaSession extension: Fix `MediaSessionConnector.play()` not resuming
- playback ([#6093](https://github.com/google/ExoPlayer/issues/6093)).
+* IMA extension:
+ * Fix behavior when creating/releasing the player then releasing
+ `ImaAdsLoader`
+ ([#3879](https://github.com/google/ExoPlayer/issues/3879)).
+ * Add support for setting slots for companion ads.
+* Captions:
+ * TTML: Fix an issue with TTML using font size as % of cell resolution
+ that makes `SubtitleView.setApplyEmbeddedFontSizes()` not work
+ correctly. ([#4491](https://github.com/google/ExoPlayer/issues/4491)).
+ * CEA-608: Improve handling of embedded styles
+ ([#4321](https://github.com/google/ExoPlayer/issues/4321)).
+* DASH:
+ * Exclude text streams from duration calculations
+ ([#4029](https://github.com/google/ExoPlayer/issues/4029)).
+ * Fix freezing when playing multi-period manifests with `EventStream`s
+ ([#4492](https://github.com/google/ExoPlayer/issues/4492)).
+* DRM: Allow DrmInitData to carry a license server URL
+ ([#3393](https://github.com/google/ExoPlayer/issues/3393)).
+* MPEG-TS: Fix bug preventing SCTE-35 cues from being output
+ ([#4573](https://github.com/google/ExoPlayer/issues/4573)).
+* Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
+ CommentFrame to InternalFrame for frames with gapless metadata in MP4.
+* Add `PlayerView.isControllerVisible`
+ ([#4385](https://github.com/google/ExoPlayer/issues/4385)).
+* Fix issue playing DRM protected streams on Asus Zenfone 2
+ ([#4403](https://github.com/google/ExoPlayer/issues/4413)).
+* Add support for multiple audio and video tracks in MPEG-PS streams
+ ([#4406](https://github.com/google/ExoPlayer/issues/4406)).
+* Add workaround for track index mismatches between trex and tkhd boxes in
+ fragmented MP4 files
+ ([#4477](https://github.com/google/ExoPlayer/issues/4477)).
+* Add workaround for track index mismatches between tfhd and tkhd boxes in
+ fragmented MP4 files
+ ([#4083](https://github.com/google/ExoPlayer/issues/4083)).
+* Ignore all MP4 edit lists if one edit list couldn't be handled
+ ([#4348](https://github.com/google/ExoPlayer/issues/4348)).
+* Fix issue when switching track selection from an embedded track to a primary
+ track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)).
+* Fix accessibility class name for `DefaultTimeBar`
+ ([#4611](https://github.com/google/ExoPlayer/issues/4611)).
+* Improved compatibility with FireOS devices.
-### 2.10.2 (2019-06-03) ###
+### 2.8.2 (2018-06-06)
-* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
- ([#5779](https://github.com/google/ExoPlayer/issues/5779)).
-* Add `SilenceMediaSource` that can be used to play silence of a given
- duration ([#5735](https://github.com/google/ExoPlayer/issues/5735)).
-* Offline:
- * Prevent unexpected `DownloadHelper.Callback.onPrepared` callbacks after
- preparation of a `DownloadHelper` fails
- ([#5915](https://github.com/google/ExoPlayer/issues/5915)).
- * Fix `CacheUtil.cache()` downloading too much data
- ([#5927](https://github.com/google/ExoPlayer/issues/5927)).
- * Fix misreporting cached bytes when caching is paused
- ([#5573](https://github.com/google/ExoPlayer/issues/5573)).
-* UI:
- * Allow setting `DefaultTimeBar` attributes on `PlayerView` and
- `PlayerControlView`.
- * Change playback controls toggle from touch down to touch up events
- ([#5784](https://github.com/google/ExoPlayer/issues/5784)).
- * Fix issue where playback controls were not kept visible on key presses
- ([#5963](https://github.com/google/ExoPlayer/issues/5963)).
-* Subtitles:
- * CEA-608: Handle XDS and TEXT modes
- ([#5807](https://github.com/google/ExoPlayer/pull/5807)).
- * TTML: Fix bitmap rendering
- ([#5633](https://github.com/google/ExoPlayer/pull/5633)).
-* IMA: Fix ad pod index offset calculation without preroll
- ([#5928](https://github.com/google/ExoPlayer/issues/5928)).
-* Add a `playWhenReady` flag to MediaSessionConnector.PlaybackPreparer methods
- to indicate whether a controller sent a play or only a prepare command. This
- allows to take advantage of decoder reuse with the MediaSessionConnector
- ([#5891](https://github.com/google/ExoPlayer/issues/5891)).
-* Add `ProgressUpdateListener` to `PlayerControlView`
- ([#5834](https://github.com/google/ExoPlayer/issues/5834)).
-* Add support for auto-detecting UDP streams in `DefaultDataSource`
- ([#6036](https://github.com/google/ExoPlayer/pull/6036)).
-* Allow enabling decoder fallback with `DefaultRenderersFactory`
- ([#5942](https://github.com/google/ExoPlayer/issues/5942)).
-* Gracefully handle revoked `ACCESS_NETWORK_STATE` permission
- ([#6019](https://github.com/google/ExoPlayer/issues/6019)).
-* Fix decoding problems when seeking back after seeking beyond a mid-roll ad
- ([#6009](https://github.com/google/ExoPlayer/issues/6009)).
-* Fix application of `maxAudioBitrate` for adaptive audio track groups
- ([#6006](https://github.com/google/ExoPlayer/issues/6006)).
-* Fix bug caused by parallel adaptive track selection using `Format`s without
- bitrate information
- ([#5971](https://github.com/google/ExoPlayer/issues/5971)).
-* Fix bug in `CastPlayer.getCurrentWindowIndex()`
- ([#5955](https://github.com/google/ExoPlayer/issues/5955)).
+* IMA extension: Don't advertise support for video/mpeg ad media, as we don't
+ have an extractor for this
+ ([#4297](https://github.com/google/ExoPlayer/issues/4297)).
+* DASH: Fix playback getting stuck when playing representations that have both
+ sidx atoms and non-zero presentationTimeOffset values.
+* HLS:
+ * Allow injection of custom playlist trackers.
+ * Fix adaptation in live playlists with EXT-X-PROGRAM-DATE-TIME tags.
+* Mitigate memory leaks when `MediaSource` loads are slow to cancel
+ ([#4249](https://github.com/google/ExoPlayer/issues/4249)).
+* Fix inconsistent `Player.EventListener` invocations for recursive player
+ state changes ([#4276](https://github.com/google/ExoPlayer/issues/4276)).
+* Fix `MediaCodec.native_setSurface` crash on Moto C
+ ([#4315](https://github.com/google/ExoPlayer/issues/4315)).
+* Fix missing whitespace in CEA-608
+ ([#3906](https://github.com/google/ExoPlayer/issues/3906)).
+* Fix crash downloading HLS media playlists
+ ([#4396](https://github.com/google/ExoPlayer/issues/4396)).
+* Fix a bug where download cancellation was ignored
+ ([#4403](https://github.com/google/ExoPlayer/issues/4403)).
+* Set `METADATA_KEY_TITLE` on media descriptions
+ ([#4292](https://github.com/google/ExoPlayer/issues/4292)).
+* Allow apps to register custom MIME types
+ ([#4264](https://github.com/google/ExoPlayer/issues/4264)).
-### 2.10.1 (2019-05-16) ###
+### 2.8.1 (2018-05-22)
-* Offline: Add option to remove all downloads.
-* HLS: Fix `NullPointerException` when using HLS chunkless preparation
- ([#5868](https://github.com/google/ExoPlayer/issues/5868)).
-* Fix handling of empty values and line terminators in SHOUTcast ICY metadata
- ([#5876](https://github.com/google/ExoPlayer/issues/5876)).
-* Fix DVB subtitles for SDK 28
- ([#5862](https://github.com/google/ExoPlayer/issues/5862)).
-* Add a workaround for a decoder failure on ZTE Axon7 mini devices when playing
- 48kHz audio ([#5821](https://github.com/google/ExoPlayer/issues/5821)).
+* HLS:
+ * Fix playback of livestreams with EXT-X-PROGRAM-DATE-TIME tags
+ ([#4239](https://github.com/google/ExoPlayer/issues/4239)).
+ * Fix playback of clipped streams starting from non-keyframe positions
+ ([#4241](https://github.com/google/ExoPlayer/issues/4241)).
+* OkHttp extension: Fix to correctly include response headers in thrown
+ `InvalidResponseCodeException`s.
+* Add possibility to cancel `PlayerMessage`s.
+* UI:
+ * Add `PlayerView.setKeepContentOnPlayerReset` to keep the currently
+ displayed video frame or media artwork visible when the player is reset
+ ([#2843](https://github.com/google/ExoPlayer/issues/2843)).
+* Fix crash when switching surface on Moto E(4)
+ ([#4134](https://github.com/google/ExoPlayer/issues/4134)).
+* Fix a bug that could cause event listeners to be called with inconsistent
+ information if an event listener interacted with the player
+ ([#4262](https://github.com/google/ExoPlayer/issues/4262)).
+* Audio:
+ * Fix extraction of PCM in MP4/MOV
+ ([#4228](https://github.com/google/ExoPlayer/issues/4228)).
+ * FLAC: Supports seeking for FLAC files without SEEKTABLE
+ ([#1808](https://github.com/google/ExoPlayer/issues/1808)).
+* Captions:
+ * TTML:
+ * Fix a styling issue when there are multiple regions displayed at the
+ same time that can make text size of each region much smaller than
+ defined.
+ * Fix an issue when the caption line has no text (empty line or only line
+ break), and the line's background is still displayed.
+ * Support TTML font size using % correctly (as percentage of document cell
+ resolution).
-### 2.10.0 (2019-04-15) ###
+### 2.8.0 (2018-05-03)
-* Core library:
- * Improve decoder re-use between playbacks
- ([#2826](https://github.com/google/ExoPlayer/issues/2826)). Read
- [this blog post](https://medium.com/google-exoplayer/improved-decoder-reuse-in-exoplayer-ef4c6d99591d)
- for more details.
- * Rename `ExtractorMediaSource` to `ProgressiveMediaSource`.
- * Fix issue where using `ProgressiveMediaSource.Factory` would mean that
- `DefaultExtractorsFactory` would be kept by proguard. Custom
- `ExtractorsFactory` instances must now be passed via the
- `ProgressiveMediaSource.Factory` constructor, and `setExtractorsFactory` is
- deprecated.
- * Make the default minimum buffer size equal the maximum buffer size for video
- playbacks ([#2083](https://github.com/google/ExoPlayer/issues/2083)).
- * Move `PriorityTaskManager` from `DefaultLoadControl` to `SimpleExoPlayer`.
- * Add new `ExoPlaybackException` types for remote exceptions and out-of-memory
- errors.
- * Use full BCP 47 language tags in `Format`.
- * Do not retry failed loads whose error is `FileNotFoundException`.
- * Fix issue where not resetting the position for a new `MediaSource` in calls
- to `ExoPlayer.prepare` causes an `IndexOutOfBoundsException`
- ([#5520](https://github.com/google/ExoPlayer/issues/5520)).
-* Offline:
- * Improve offline support. `DownloadManager` now tracks all offline content,
- not just tasks in progress. Read
- [this page](https://exoplayer.dev/downloading-media.html) for more details.
-* Caching:
- * Improve performance of `SimpleCache`
- ([#4253](https://github.com/google/ExoPlayer/issues/4253)).
- * Cache data with unknown length by default. The previous flag to opt in to
- this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
- replaced with an opt out flag
- (`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`).
-* Extractors:
- * MP4/FMP4: Add support for Dolby Vision.
- * MP4: Fix issue handling meta atoms in some streams
- ([#5698](https://github.com/google/ExoPlayer/issues/5698),
- [#5694](https://github.com/google/ExoPlayer/issues/5694)).
- * MP3: Add support for SHOUTcast ICY metadata
- ([#3735](https://github.com/google/ExoPlayer/issues/3735)).
- * MP3: Fix ID3 frame unsychronization
- ([#5673](https://github.com/google/ExoPlayer/issues/5673)).
- * MP3: Fix playback of badly clipped files
- ([#5772](https://github.com/google/ExoPlayer/issues/5772)).
- * MPEG-TS: Enable HDMV DTS stream detection only if a flag is set. By default
- (i.e. if the flag is not set), the 0x82 elementary stream type is now
- treated as an SCTE subtitle track
- ([#5330](https://github.com/google/ExoPlayer/issues/5330)).
-* Track selection:
- * Add options for controlling audio track selections to `DefaultTrackSelector`
- ([#3314](https://github.com/google/ExoPlayer/issues/3314)).
- * Update `TrackSelection.Factory` interface to support creating all track
- selections together.
- * Allow to specify a selection reason for a `SelectionOverride`.
- * Select audio track based on system language if no preference is provided.
- * When no text language preference matches, only select forced text tracks
- whose language matches the selected audio language.
-* UI:
- * Update `DefaultTimeBar` based on duration of media and add parameter to set
- the minimum update interval to control the smoothness of the updates
- ([#5040](https://github.com/google/ExoPlayer/issues/5040)).
- * Move creation of dialogs for `TrackSelectionView`s to
- `TrackSelectionDialogBuilder` and add option to select multiple overrides.
- * Change signature of `PlayerNotificationManager.NotificationListener` to
- better fit service requirements.
- * Add option to include navigation actions in the compact mode of
- notifications created using `PlayerNotificationManager`.
- * Fix issues with flickering notifications on KitKat when using
- `PlayerNotificationManager` and `DownloadNotificationUtil`. For the latter,
- applications should switch to using `DownloadNotificationHelper`.
- * Fix accuracy of D-pad seeking in `DefaultTimeBar`
- ([#5767](https://github.com/google/ExoPlayer/issues/5767)).
-* Audio:
- * Allow `AudioProcessor`s to be drained of pending output after they are
- reconfigured.
- * Fix an issue that caused audio to be truncated at the end of a period
- when switching to a new period where gapless playback information was newly
- present or newly absent.
- * Add support for reading AC-4 streams
- ([#5303](https://github.com/google/ExoPlayer/pull/5303)).
-* Video:
- * Remove `MediaCodecSelector.DEFAULT_WITH_FALLBACK`. Apps should instead
- signal that fallback should be used by passing `true` as the
- `enableDecoderFallback` parameter when instantiating the video renderer.
- * Support video tunneling when the decoder is not listed first for the MIME
- type ([#3100](https://github.com/google/ExoPlayer/issues/3100)).
- * Query `MediaCodecList.ALL_CODECS` when selecting a tunneling decoder
- ([#5547](https://github.com/google/ExoPlayer/issues/5547)).
-* DRM:
- * Fix black flicker when keys rotate in DRM protected content
- ([#3561](https://github.com/google/ExoPlayer/issues/3561)).
- * Work around lack of LA_URL attribute in PlayReady key request init data.
-* CEA-608: Improved conformance to the specification
- ([#3860](https://github.com/google/ExoPlayer/issues/3860)).
-* DASH:
- * Parse role and accessibility descriptors into `Format.roleFlags`.
- * Support multiple CEA-608 channels muxed into FMP4 representations
- ([#5656](https://github.com/google/ExoPlayer/issues/5656)).
-* HLS:
- * Prevent unnecessary reloads of initialization segments.
- * Form an adaptive track group out of audio renditions with matching name.
- * Support encrypted initialization segments
- ([#5441](https://github.com/google/ExoPlayer/issues/5441)).
- * Parse `EXT-X-MEDIA` `CHARACTERISTICS` attribute into `Format.roleFlags`.
- * Add metadata entry for HLS tracks to expose master playlist information.
- * Prevent `IndexOutOfBoundsException` in some live HLS scenarios
- ([#5816](https://github.com/google/ExoPlayer/issues/5816)).
-* Support for playing spherical videos on Daydream.
-* Cast extension: Work around Cast framework returning a limited-size queue
- items list ([#4964](https://github.com/google/ExoPlayer/issues/4964)).
-* VP9 extension: Remove RGB output mode and libyuv dependency, and switch to
- surface YUV output as the default. Remove constructor parameters `scaleToFit`
- and `useSurfaceYuvOutput`.
-* MediaSession extension:
- * Let apps intercept media button events
- ([#5179](https://github.com/google/ExoPlayer/issues/5179)).
- * Fix issue with `TimelineQueueNavigator` not publishing the queue in shuffled
- order when in shuffle mode.
- * Allow handling of custom commands via `registerCustomCommandReceiver`.
- * Add ability to include an extras `Bundle` when reporting a custom error.
-* Log warnings when extension native libraries can't be used, to help with
- diagnosing playback failures
- ([#5788](https://github.com/google/ExoPlayer/issues/5788)).
+* Downloading:
+ * Add `DownloadService`, `DownloadManager` and related classes
+ ([#2643](https://github.com/google/ExoPlayer/issues/2643)). Information
+ on using these components to download progressive formats can be found
+ [here](https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95).
+ To see how to download DASH, HLS and SmoothStreaming media, take a look
+ at the app.
+ * Updated main demo app to support downloading DASH, HLS, SmoothStreaming
+ and progressive media.
+* MediaSources:
+ * Allow reusing media sources after they have been released and also in
+ parallel to allow adding them multiple times to a concatenation.
+ ([#3498](https://github.com/google/ExoPlayer/issues/3498)).
+ * Merged `DynamicConcatenatingMediaSource` into `ConcatenatingMediaSource`
+ and deprecated `DynamicConcatenatingMediaSource`.
+ * Allow clipping of child media sources where the period and window have a
+ non-zero offset with `ClippingMediaSource`.
+ * Allow adding and removing `MediaSourceEventListener`s to MediaSources
+ after they have been created. Listening to events is now supported for
+ all media sources including composite sources.
+ * Added callbacks to `MediaSourceEventListener` to get notified when media
+ periods are created, released and being read from.
+ * Support live stream clipping with `ClippingMediaSource`.
+ * Allow setting tags for all media sources in their factories. The tag of
+ the current window can be retrieved with `Player.getCurrentTag`.
+* UI:
+ * Add support for displaying error messages and a buffering spinner in
+ `PlayerView`.
+ * Add support for listening to `AspectRatioFrameLayout`'s aspect ratio
+ update ([#3736](https://github.com/google/ExoPlayer/issues/3736)).
+ * Add `PlayerNotificationManager` for displaying notifications reflecting
+ the player state.
+ * Add `TrackSelectionView` for selecting tracks with
+ `DefaultTrackSelector`.
+ * Add `TrackNameProvider` for converting track `Format`s to textual
+ descriptions, and `DefaultTrackNameProvider` as a default
+ implementation.
+* Track selection:
+ * Reworked `MappingTrackSelector` and `DefaultTrackSelector`.
+ * `DefaultTrackSelector.Parameters` now implements `Parcelable`.
+ * Added UI components for track selection (see above).
+* Audio:
+ * Support extracting data from AMR container formats, including both
+ narrow and wide band
+ ([#2527](https://github.com/google/ExoPlayer/issues/2527)).
+ * FLAC:
+ * Sniff FLAC files correctly if they have ID3 headers
+ ([#4055](https://github.com/google/ExoPlayer/issues/4055)).
+ * Supports FLAC files with high sample rate (176400 and 192000)
+ ([#3769](https://github.com/google/ExoPlayer/issues/3769)).
+ * Factor out `AudioTrack` position tracking from `DefaultAudioSink`.
+ * Fix an issue where the playback position would pause just after playback
+ begins, and poll the audio timestamp less frequently once it starts
+ advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
+ * Add an option to skip silent audio in `PlaybackParameters`
+ ([#2635](https://github.com/google/ExoPlayer/issues/2635)).
+ * Fix an issue where playback of TrueHD streams would get stuck after
+ seeking due to not finding a syncframe
+ ([#3845](https://github.com/google/ExoPlayer/issues/3845)).
+ * Fix an issue with eac3-joc playback where a codec would fail to
+ configure ([#4165](https://github.com/google/ExoPlayer/issues/4165)).
+ * Handle non-empty end-of-stream buffers, to fix gapless playback of
+ streams with encoder padding when the decoder returns a non-empty final
+ buffer.
+ * Allow trimming more than one sample when applying an elst audio edit via
+ gapless playback info.
+ * Allow overriding skipping/scaling with custom `AudioProcessor`s
+ ([#3142](https://github.com/google/ExoPlayer/issues/3142)).
+* Caching:
+ * Add release method to the `Cache` interface, and prevent multiple
+ instances of `SimpleCache` using the same folder at the same time.
+ * Cache redirect URLs
+ ([#2360](https://github.com/google/ExoPlayer/issues/2360)).
+* DRM:
+ * Allow multiple listeners for `DefaultDrmSessionManager`.
+ * Pass `DrmSessionManager` to `ExoPlayerFactory` instead of
+ `RendererFactory`.
+ * Change minimum API requirement for CBC and pattern encryption from 24 to
+ 25 ([#4022](https://github.com/google/ExoPlayer/issues/4022)).
+ * Fix handling of 307/308 redirects when making license requests
+ ([#4108](https://github.com/google/ExoPlayer/issues/4108)).
+* HLS:
+ * Fix playlist loading error propagation when the current selection does
+ not include all of the playlist's variants.
+ * Fix SAMPLE-AES-CENC and SAMPLE-AES-CTR EXT-X-KEY methods
+ ([#4145](https://github.com/google/ExoPlayer/issues/4145)).
+ * Preeptively declare an ID3 track in chunkless preparation
+ ([#4016](https://github.com/google/ExoPlayer/issues/4016)).
+ * Add support for multiple #EXT-X-MAP tags in a media playlist
+ ([#4164](https://github.com/google/ExoPlayer/issues/4182)).
+ * Fix seeking in live streams
+ ([#4187](https://github.com/google/ExoPlayer/issues/4187)).
+* IMA extension:
+ * Allow setting the ad media load timeout
+ ([#3691](https://github.com/google/ExoPlayer/issues/3691)).
+ * Expose ad load errors via `MediaSourceEventListener` on
+ `AdsMediaSource`, and allow setting an ad event listener on
+ `ImaAdsLoader`. Deprecate the `AdsMediaSource.EventListener`.
+* Add `AnalyticsListener` interface which can be registered in
+ `SimpleExoPlayer` to receive detailed metadata for each ExoPlayer event.
+* Optimize seeking in FMP4 by enabling seeking to the nearest sync sample
+ within a fragment. This benefits standalone FMP4 playbacks, DASH and
+ SmoothStreaming.
+* Updated default max buffer length in `DefaultLoadControl`.
+* Fix ClearKey decryption error if the key contains a forward slash
+ ([#4075](https://github.com/google/ExoPlayer/issues/4075)).
+* Fix crash when switching surface on Huawei P9 Lite
+ ([#4084](https://github.com/google/ExoPlayer/issues/4084)), and Philips
+ QM163E ([#4104](https://github.com/google/ExoPlayer/issues/4104)).
+* Support ZLIB compressed PGS subtitles.
+* Added `getPlaybackError` to `Player` interface.
+* Moved initial bitrate estimate from `AdaptiveTrackSelection` to
+ `DefaultBandwidthMeter`.
+* Removed default renderer time offset of 60000000 from internal player. The
+ actual renderer timestamp offset can be obtained by listening to
+ `BaseRenderer.onStreamChanged`.
+* Added dependencies on checkerframework annotations for static code analysis.
-### 2.9.6 (2019-02-19) ###
+### 2.7.3 (2018-04-04)
-* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`.
-* IMA extension:
- * Require setting the `Player` on `AdsLoader` instances before
- playback.
- * Remove deprecated `ImaAdsMediaSource`. Create `AdsMediaSource` with an
- `ImaAdsLoader` instead.
- * Remove deprecated `AdsMediaSource` constructors. Listen for media source
- events using `AdsMediaSource.addEventListener`, and ad interaction events by
- adding a listener when building `ImaAdsLoader`.
- * Allow apps to register playback-related obstructing views that are on top of
- their ad display containers via `AdsLoader.AdViewProvider`. `PlayerView`
- implements this interface and will register its control view. This makes it
- possible for ad loading SDKs to calculate ad viewability accurately.
-* DASH: Fix issue handling large `EventStream` presentation timestamps
- ([#5490](https://github.com/google/ExoPlayer/issues/5490)).
-* HLS: Fix transition to STATE_ENDED when playing fragmented mp4 in chunkless
- preparation ([#5524](https://github.com/google/ExoPlayer/issues/5524)).
-* Revert workaround for video quality problems with Amlogic decoders, as this
- may cause problems for some devices and/or non-interlaced content
- ([#5003](https://github.com/google/ExoPlayer/issues/5003)).
+* Fix ProGuard configuration for Cast, IMA and OkHttp extensions.
+* Update OkHttp extension to depend on OkHttp 3.10.0.
-### 2.9.5 (2019-01-31) ###
+### 2.7.2 (2018-03-29)
-* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
-* ConcatenatingMediaSource:
- * Add `Handler` parameter to methods that take a callback `Runnable`.
- * Fix issue with dropped messages when releasing the source
- ([#5464](https://github.com/google/ExoPlayer/issues/5464)).
-* ExtractorMediaSource: Fix issue that could cause the player to get stuck
- buffering at the end of the media.
-* PlayerView: Fix issue preventing `OnClickListener` from receiving events
- ([#5433](https://github.com/google/ExoPlayer/issues/5433)).
-* IMA extension: Upgrade IMA dependency to 3.10.6.
-* Cronet extension: Upgrade Cronet dependency to 71.3578.98.
-* OkHttp extension: Upgrade OkHttp dependency to 3.12.1.
-* MP3: Wider fix for issue where streams would play twice on some Samsung
- devices ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
+* Gradle: Upgrade Gradle version from 4.1 to 4.4 so it can work with Android
+ Studio 3.1 ([#3708](https://github.com/google/ExoPlayer/issues/3708)).
+* Match codecs starting with "mp4a" to different Audio MimeTypes
+ ([#3779](https://github.com/google/ExoPlayer/issues/3779)).
+* Fix ANR issue on Redmi 4X and Redmi Note 4
+ ([#4006](https://github.com/google/ExoPlayer/issues/4006)).
+* Fix handling of zero padded strings when parsing Matroska streams
+ ([#4010](https://github.com/google/ExoPlayer/issues/4010)).
+* Fix "Decoder input buffer too small" error when playing some FLAC streams.
+* MediaSession extension: Omit fast forward and rewind actions when media is
+ not seekable ([#4001](https://github.com/google/ExoPlayer/issues/4001)).
-### 2.9.4 (2019-01-15) ###
+### 2.7.1 (2018-03-09)
-* IMA extension: Clear ads loader listeners on release
- ([#4114](https://github.com/google/ExoPlayer/issues/4114)).
-* SmoothStreaming: Fix support for subtitles in DRM protected streams
- ([#5378](https://github.com/google/ExoPlayer/issues/5378)).
-* FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior
- of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)).
-* GVR extension: upgrade GVR SDK dependency to 1.190.0.
-* Associate fatal player errors of type SOURCE with the loading source in
- `AnalyticsListener.EventTime`
- ([#5407](https://github.com/google/ExoPlayer/issues/5407)).
-* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where
- using lazy preparation in `ConcatenatingMediaSource` with an
- `ExtractorMediaSource` overrides initial seek positions
- ([#5350](https://github.com/google/ExoPlayer/issues/5350)).
-* Add subtext to the `MediaDescriptionAdapter` of the
- `PlayerNotificationManager`.
-* Add workaround for video quality problems with Amlogic decoders
- ([#5003](https://github.com/google/ExoPlayer/issues/5003)).
-* Fix issue where sending callbacks for playlist changes may cause problems
- because of parallel player access
- ([#5240](https://github.com/google/ExoPlayer/issues/5240)).
-* Fix issue with reusing a `ClippingMediaSource` with an inner
- `ExtractorMediaSource` and a non-zero start position
- ([#5351](https://github.com/google/ExoPlayer/issues/5351)).
-* Fix issue where uneven track durations in MP4 streams can cause OOM problems
- ([#3670](https://github.com/google/ExoPlayer/issues/3670)).
+* Gradle: Replaced 'compile' (deprecated) with 'implementation' and 'api'.
+ This may lead to build breakage for applications upgrading from previous
+ version that rely on indirect dependencies of certain modules. In such
+ cases, application developers need to add the missing dependency to their
+ gradle file. You can read more about the new dependency configurations
+ [here](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#new_configurations).
+* HlsMediaSource: Make HLS periods start at zero instead of the epoch.
+ Applications that rely on HLS timelines having a period starting at the
+ epoch will need to update their handling of HLS timelines. The program date
+ time is still available via the informational
+ `Timeline.Window.windowStartTimeMs` field
+ ([#3865](https://github.com/google/ExoPlayer/issues/3865),
+ [#3888](https://github.com/google/ExoPlayer/issues/3888)).
+* Enable seeking in MP4 streams where duration is set incorrectly in the track
+ header ([#3926](https://github.com/google/ExoPlayer/issues/3926)).
+* Video: Force rendering a frame periodically in `MediaCodecVideoRenderer` and
+ `LibvpxVideoRenderer`, even if it is late.
-### 2.9.3 (2018-12-20) ###
+### 2.7.0 (2018-02-19)
-* Captions: Support PNG subtitles in SMPTE-TT
- ([#1583](https://github.com/google/ExoPlayer/issues/1583)).
-* MPEG-TS: Use random access indicators to minimize the need for
- `FLAG_ALLOW_NON_IDR_KEYFRAMES`.
-* Downloading: Reduce time taken to remove downloads
- ([#5136](https://github.com/google/ExoPlayer/issues/5136)).
-* MP3:
- * Use the true bitrate for constant-bitrate MP3 seeking.
- * Fix issue where streams would play twice on some Samsung devices
- ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
-* Fix regression where some audio formats were incorrectly marked as being
- unplayable due to under-reporting of platform decoder capabilities
- ([#5145](https://github.com/google/ExoPlayer/issues/5145)).
-* Fix decode-only frame skipping on Nvidia Shield TV devices.
-* Workaround for MiTV (dangal) issue when swapping output surface
- ([#5169](https://github.com/google/ExoPlayer/issues/5169)).
+* Player interface:
+ * Add optional parameter to `stop` to reset the player when stopping.
+ * Add a reason to `EventListener.onTimelineChanged` to distinguish between
+ initial preparation, reset and dynamic updates.
+ * Add `Player.DISCONTINUITY_REASON_AD_INSERTION` to the possible reasons
+ reported in `Eventlistener.onPositionDiscontinuity` to distinguish
+ transitions to and from ads within one period from transitions between
+ periods.
+ * Replaced `ExoPlayer.sendMessages` with `ExoPlayer.createMessage` to
+ allow more customization of the message. Now supports setting a message
+ delivery playback position and/or a delivery handler
+ ([#2189](https://github.com/google/ExoPlayer/issues/2189)).
+ * Add `Player.VideoComponent`, `Player.TextComponent` and
+ `Player.MetadataComponent` interfaces that define optional video, text
+ and metadata output functionality. New `getVideoComponent`,
+ `getTextComponent` and `getMetadataComponent` methods provide access to
+ this functionality.
+* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are
+ performed. The `SeekParameters` class contains defaults for exact seeking
+ and seeking to the closest sync points before, either side or after
+ specified seek positions. `SeekParameters` are not currently supported when
+ playing HLS streams.
+* DefaultTrackSelector:
+ * Replace `DefaultTrackSelector.Parameters` copy methods with a builder.
+ * Support disabling of individual text track selection flags.
+* Buffering:
+ * Allow a back-buffer of media to be retained behind the current playback
+ position, for fast backward seeking. The back-buffer can be configured
+ by custom `LoadControl` implementations.
+ * Add ability for `SequenceableLoader` to re-evaluate its buffer and
+ discard buffered media so that it can be re-buffered in a different
+ quality.
+ * Allow more flexible loading strategy when playing media containing
+ multiple sub-streams, by allowing injection of custom
+ `CompositeSequenceableLoader` factories through
+ `DashMediaSource.Factory`, `HlsMediaSource.Factory`,
+ `SsMediaSource.Factory`, and `MergingMediaSource`.
+ * Play out existing buffer before retrying for progressive live streams
+ ([#1606](https://github.com/google/ExoPlayer/issues/1606)).
+* UI:
+ * Generalized player and control views to allow them to bind with any
+ `Player`, and renamed them to `PlayerView` and `PlayerControlView`
+ respectively.
+ * Made `PlayerView` automatically apply video rotation when configured to
+ use `TextureView`
+ ([#91](https://github.com/google/ExoPlayer/issues/91)).
+ * Made `PlayerView` play button behave correctly when the player is ended
+ ([#3689](https://github.com/google/ExoPlayer/issues/3689)), and call a
+ `PlaybackPreparer` when the player is idle.
+* DRM: Optimistically attempt playback of DRM protected content that does not
+ declare scheme specific init data in the manifest. If playback of clear
+ samples without keys is allowed, delay DRM session error propagation until
+ keys are actually needed
+ ([#3630](https://github.com/google/ExoPlayer/issues/3630)).
+* DASH:
+ * Support in-band Emsg events targeting the player with scheme id
+ `urn:mpeg:dash:event:2012` and scheme values "1", "2" and "3".
+ * Support EventStream elements in DASH manifests.
+* HLS:
+ * Add opt-in support for chunkless preparation in HLS. This allows an HLS
+ source to finish preparation without downloading any chunks, which can
+ significantly reduce initial buffering time
+ ([#3149](https://github.com/google/ExoPlayer/issues/3149)). More details
+ can be found
+ [here](https://medium.com/google-exoplayer/faster-hls-preparation-f6611aa15ea6).
+ * Fail if unable to sync with the Transport Stream, rather than entering
+ stuck in an indefinite buffering state.
+ * Fix mime type propagation
+ ([#3653](https://github.com/google/ExoPlayer/issues/3653)).
+ * Fix ID3 context reuse across segment format changes
+ ([#3622](https://github.com/google/ExoPlayer/issues/3622)).
+ * Use long for media sequence numbers
+ ([#3747](https://github.com/google/ExoPlayer/issues/3747))
+ * Add initial support for the EXT-X-GAP tag.
+* Audio:
+ * Support TrueHD passthrough for rechunked samples in Matroska files
+ ([#2147](https://github.com/google/ExoPlayer/issues/2147)).
+ * Support resampling 24-bit and 32-bit integer to 32-bit float for high
+ resolution output in `DefaultAudioSink`
+ ([#3635](https://github.com/google/ExoPlayer/pull/3635)).
+* Captions:
+ * Basic support for PGS subtitles
+ ([#3008](https://github.com/google/ExoPlayer/issues/3008)).
+ * Fix handling of CEA-608 captions where multiple buffers have the same
+ presentation timestamp
+ ([#3782](https://github.com/google/ExoPlayer/issues/3782)).
+* Caching:
+ * Fix cache corruption issue
+ ([#3762](https://github.com/google/ExoPlayer/issues/3762)).
+ * Implement periodic check in `CacheDataSource` to see whether it's
+ possible to switch to reading/writing the cache having initially
+ bypassed it.
+* IMA extension:
+ * Fix the player getting stuck when an ad group fails to load
+ ([#3584](https://github.com/google/ExoPlayer/issues/3584)).
+ * Work around loadAd not being called beore the LOADED AdEvent arrives
+ ([#3552](https://github.com/google/ExoPlayer/issues/3552)).
+ * Handle asset mismatch errors
+ ([#3801](https://github.com/google/ExoPlayer/issues/3801)).
+ * Add support for playing non-Extractor content MediaSources in the IMA
+ demo app ([#3676](https://github.com/google/ExoPlayer/issues/3676)).
+ * Fix handling of ad tags where ad groups are out of order
+ ([#3716](https://github.com/google/ExoPlayer/issues/3716)).
+ * Fix handling of ad tags with only preroll/postroll ad groups
+ ([#3715](https://github.com/google/ExoPlayer/issues/3715)).
+ * Propagate ad media preparation errors to IMA so that the ads can be
+ skipped.
+ * Handle exceptions in IMA callbacks so that can be logged less verbosely.
+* New Cast extension. Simplifies toggling between local and Cast playbacks.
+* `EventLogger` moved from the demo app into the core library.
+* Fix ANR issue on the Huawei P8 Lite, Huawei Y6II, Moto C+, Meizu M5C, Lenovo
+ K4 Note and Sony Xperia E5.
+ ([#3724](https://github.com/google/ExoPlayer/issues/3724),
+ [#3835](https://github.com/google/ExoPlayer/issues/3835)).
+* Fix potential NPE when removing media sources from a
+ DynamicConcatenatingMediaSource
+ ([#3796](https://github.com/google/ExoPlayer/issues/3796)).
+* Check `sys.display-size` on Philips ATVs
+ ([#3807](https://github.com/google/ExoPlayer/issues/3807)).
+* Release `Extractor`s on the loading thread to avoid potentially leaking
+ resources when the playback thread has quit by the time the loading task has
+ completed.
+* ID3: Better handle malformed ID3 data
+ ([#3792](https://github.com/google/ExoPlayer/issues/3792).
+* Support 14-bit mode and little endianness in DTS PES packets
+ ([#3340](https://github.com/google/ExoPlayer/issues/3340)).
+* Demo app: Add ability to download not DRM protected content.
-### 2.9.2 (2018-11-28) ###
+### 2.6.1 (2017-12-15)
-* HLS:
- * Fix issue causing unnecessary media playlist requests when playing live
- streams ([#5059](https://github.com/google/ExoPlayer/issues/5059)).
- * Fix decoder re-instantiation issue for packed audio streams
- ([#5063](https://github.com/google/ExoPlayer/issues/5063)).
-* MP4: Support Opus and FLAC in the MP4 container, and in DASH
- ([#4883](https://github.com/google/ExoPlayer/issues/4883)).
-* DASH: Fix detecting the end of live events
- ([#4780](https://github.com/google/ExoPlayer/issues/4780)).
-* Spherical video: Fall back to `TYPE_ROTATION_VECTOR` if
- `TYPE_GAME_ROTATION_VECTOR` is unavailable
- ([#5119](https://github.com/google/ExoPlayer/issues/5119)).
-* Support seeking for a wider range of MPEG-TS streams
- ([#5097](https://github.com/google/ExoPlayer/issues/5097)).
-* Include channel count in audio capabilities check
- ([#4690](https://github.com/google/ExoPlayer/issues/4690)).
-* Fix issue with applying the `show_buffering` attribute in `PlayerView`
- ([#5139](https://github.com/google/ExoPlayer/issues/5139)).
-* Fix issue where null `Metadata` was output when it failed to decode
- ([#5149](https://github.com/google/ExoPlayer/issues/5149)).
-* Fix playback of some invalid but playable MP4 streams by replacing assertions
- with logged warnings in sample table parsing code
- ([#5162](https://github.com/google/ExoPlayer/issues/5162)).
-* Fix UUID passed to `MediaCrypto` when using `C.CLEARKEY_UUID` before API 27.
+* Add factories to `ExtractorMediaSource`, `HlsMediaSource`, `SsMediaSource`,
+ `DashMediaSource` and `SingleSampleMediaSource`.
+* Use the same listener `MediaSourceEventListener` for all MediaSource
+ implementations.
+* IMA extension:
+ * Support non-ExtractorMediaSource ads
+ ([#3302](https://github.com/google/ExoPlayer/issues/3302)).
+ * Skip ads before the ad preceding the player's initial seek position
+ ([#3527](https://github.com/google/ExoPlayer/issues/3527)).
+ * Fix ad loading when there is no preroll.
+ * Add an option to turn off hiding controls during ad playback
+ ([#3532](https://github.com/google/ExoPlayer/issues/3532)).
+ * Support specifying an ads response instead of an ad tag
+ ([#3548](https://github.com/google/ExoPlayer/issues/3548)).
+ * Support overriding the ad load timeout
+ ([#3556](https://github.com/google/ExoPlayer/issues/3556)).
+* DASH: Support time zone designators in ISO8601 UTCTiming elements
+ ([#3524](https://github.com/google/ExoPlayer/issues/3524)).
+* Audio:
+ * Support 32-bit PCM float output from `DefaultAudioSink`, and add an
+ option to use this with `FfmpegAudioRenderer`.
+ * Add support for extracting 32-bit WAVE files
+ ([#3379](https://github.com/google/ExoPlayer/issues/3379)).
+ * Support extraction and decoding of Dolby Atmos
+ ([#2465](https://github.com/google/ExoPlayer/issues/2465)).
+ * Fix handling of playback parameter changes while paused when followed by
+ a seek.
+* SimpleExoPlayer: Allow multiple audio and video debug listeners.
+* DefaultTrackSelector: Support undefined language text track selection when
+ the preferred language is not available
+ ([#2980](https://github.com/google/ExoPlayer/issues/2980)).
+* Add options to `DefaultLoadControl` to set maximum buffer size in bytes and
+ to choose whether size or time constraints are prioritized.
+* Use surfaceless context for secure `DummySurface`, if available
+ ([#3558](https://github.com/google/ExoPlayer/issues/3558)).
+* FLV: Fix playback of live streams that do not contain an audio track
+ ([#3188](https://github.com/google/ExoPlayer/issues/3188)).
+* CEA-608: Fix handling of row count changes in roll-up mode
+ ([#3513](https://github.com/google/ExoPlayer/issues/3513)).
+* Prevent period transitions when seeking to the end of a period when paused
+ ([#2439](https://github.com/google/ExoPlayer/issues/2439)).
-### 2.9.1 (2018-11-01) ###
+### 2.6.0 (2017-11-03)
-* Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext`
- and `Player.hasPrevious`
- ([#4863](https://github.com/google/ExoPlayer/issues/4863)).
-* Improve initial bandwidth meter estimates using the current country and
- network type.
-* IMA extension:
- * For preroll to live stream transitions, project forward the loading position
- to avoid being behind the live window.
- * Let apps specify whether to focus the skip button on ATV
- ([#5019](https://github.com/google/ExoPlayer/issues/5019)).
-* MP3:
- * Support seeking based on MLLT metadata
- ([#3241](https://github.com/google/ExoPlayer/issues/3241)).
- * Fix handling of streams with appended data
- ([#4954](https://github.com/google/ExoPlayer/issues/4954)).
-* DASH: Parse ProgramInformation element if present in the manifest.
-* HLS:
- * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload
- reader factory flags
- ([#4861](https://github.com/google/ExoPlayer/issues/4861)).
- * Fix bug in segment sniffing
- ([#5039](https://github.com/google/ExoPlayer/issues/5039)).
-* SubRip: Add support for alignment tags, and remove tags from the displayed
- captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
-* Fix issue with blind seeking to windows with non-zero offset in a
- `ConcatenatingMediaSource`
- ([#4873](https://github.com/google/ExoPlayer/issues/4873)).
-* Fix logic for enabling next and previous actions in `TimelineQueueNavigator`
- ([#5065](https://github.com/google/ExoPlayer/issues/5065)).
-* Fix issue where audio focus handling could not be disabled after enabling it
- ([#5055](https://github.com/google/ExoPlayer/issues/5055)).
-* Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a
- non-zero position offset to its parent
- ([#4788](https://github.com/google/ExoPlayer/issues/4788)).
-* Fix issue where the buffered position was not updated correctly when
- transitioning between periods
- ([#4899](https://github.com/google/ExoPlayer/issues/4899)).
-* Fix issue where a `NullPointerException` is thrown when removing an unprepared
- media source from a `ConcatenatingMediaSource` with the `useLazyPreparation`
- option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)).
-* Work around an issue where a non-empty end-of-stream audio buffer would be
- output with timestamp zero, causing the player position to jump backwards
- ([#5045](https://github.com/google/ExoPlayer/issues/5045)).
-* Suppress a spurious assertion failure on some Samsung devices
- ([#4532](https://github.com/google/ExoPlayer/issues/4532)).
-* Suppress spurious "references unknown class member" shrinking warning
- ([#4890](https://github.com/google/ExoPlayer/issues/4890)).
-* Swap recommended order for google() and jcenter() in gradle config
- ([#4997](https://github.com/google/ExoPlayer/issues/4997)).
+* Removed "r" prefix from versions. This release is "2.6.0", not "r2.6.0".
+* New `Player.DefaultEventListener` abstract class can be extended to avoid
+ having to implement all methods defined by `Player.EventListener`.
+* Added a reason to `EventListener.onPositionDiscontinuity`
+ ([#3252](https://github.com/google/ExoPlayer/issues/3252)).
+* New `setShuffleModeEnabled` method for enabling shuffled playback.
+* SimpleExoPlayer: Support for multiple video, text and metadata outputs.
+* Support for `Renderer`s that don't consume any media
+ ([#3212](https://github.com/google/ExoPlayer/issues/3212)).
+* Fix reporting of internal position discontinuities via
+ `Player.onPositionDiscontinuity`. `DISCONTINUITY_REASON_SEEK_ADJUSTMENT` is
+ added to disambiguate position adjustments during seeks from other types of
+ internal position discontinuity.
+* Fix potential `IndexOutOfBoundsException` when calling
+ `ExoPlayer.getDuration`
+ ([#3362](https://github.com/google/ExoPlayer/issues/3362)).
+* Fix playbacks involving looping, concatenation and ads getting stuck when
+ media contains tracks with uneven durations
+ ([#1874](https://github.com/google/ExoPlayer/issues/1874)).
+* Fix issue with `ContentDataSource` when reading from certain
+ `ContentProvider` implementations
+ ([#3426](https://github.com/google/ExoPlayer/issues/3426)).
+* Better playback experience when the video decoder cannot keep up, by
+ skipping to key-frames. This is particularly relevant for variable speed
+ playbacks.
+* Allow `SingleSampleMediaSource` to suppress load errors
+ ([#3140](https://github.com/google/ExoPlayer/issues/3140)).
+* `DynamicConcatenatingMediaSource`: Allow specifying a callback to be invoked
+ after a dynamic playlist modification has been applied
+ ([#3407](https://github.com/google/ExoPlayer/issues/3407)).
+* Audio: New `AudioSink` interface allows customization of audio output path.
+* Offline: Added `Downloader` implementations for DASH, HLS, SmoothStreaming
+ and progressive streams.
+* Track selection:
+ * Fixed adaptive track selection logic for live playbacks
+ ([#3017](https://github.com/google/ExoPlayer/issues/3017)).
+ * Added ability to select the lowest bitrate tracks.
+* DASH:
+ * Don't crash when a malformed or unexpected manifest update occurs
+ ([#2795](https://github.com/google/ExoPlayer/issues/2795)).
+* HLS:
+ * Support for Widevine protected FMP4 variants.
+ * Support CEA-608 in FMP4 variants.
+ * Support extractor injection
+ ([#2748](https://github.com/google/ExoPlayer/issues/2748)).
+* DRM:
+ * Improved compatibility with ClearKey content
+ ([#3138](https://github.com/google/ExoPlayer/issues/3138)).
+ * Support multiple PSSH boxes of the same type.
+ * Retry initial provisioning and key requests if they fail
+ * Fix incorrect parsing of non-CENC sinf boxes.
+* IMA extension:
+ * Expose `AdsLoader` via getter
+ ([#3322](https://github.com/google/ExoPlayer/issues/3322)).
+ * Handle `setPlayWhenReady` calls during ad playbacks
+ ([#3303](https://github.com/google/ExoPlayer/issues/3303)).
+ * Ignore seeks if an ad is playing
+ ([#3309](https://github.com/google/ExoPlayer/issues/3309)).
+ * Improve robustness of `ImaAdsLoader` in case content is not paused
+ between content to ad transitions
+ ([#3430](https://github.com/google/ExoPlayer/issues/3430)).
+* UI:
+ * Allow specifying a `Drawable` for the `TimeBar` scrubber
+ ([#3337](https://github.com/google/ExoPlayer/issues/3337)).
+ * Allow multiple listeners on `TimeBar`
+ ([#3406](https://github.com/google/ExoPlayer/issues/3406)).
+* New Leanback extension: Simplifies binding Exoplayer to Leanback UI
+ components.
+* Unit tests moved to Robolectric.
+* Misc bugfixes.
-### 2.9.0 (2018-09-06) ###
+### r2.5.4 (2017-10-19)
-* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to
- add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
- gradle settings to ensure bytecode compatibility.
-* Set `compileSdkVersion` and `targetSdkVersion` to 28.
-* Support for automatic audio focus handling via
- `SimpleExoPlayer.setAudioAttributes`.
-* Add `ExoPlayer.retry` convenience method.
-* Add `AudioListener` for listening to changes in audio configuration during
- playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)).
-* Add `LoadErrorHandlingPolicy` to allow configuration of load error handling
- across `MediaSource` implementations
- ([#3370](https://github.com/google/ExoPlayer/issues/3370)).
-* Allow passing a `Looper`, which specifies the thread that must be used to
- access the player, when instantiating player instances using
- `ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)).
-* Allow setting log level for ExoPlayer logcat output
- ([#4665](https://github.com/google/ExoPlayer/issues/4665)).
-* Simplify `BandwidthMeter` injection: The `BandwidthMeter` should now be
- passed directly to `ExoPlayerFactory`, instead of to `TrackSelection.Factory`
- and `DataSource.Factory`. The `BandwidthMeter` is passed to the components
- that need it internally. The `BandwidthMeter` may also be omitted, in which
- case a default instance will be used.
-* Spherical video:
- * Support for spherical video by setting `surface_type="spherical_view"` on
- `PlayerView`.
- * Support for
- [VR180](https://github.com/google/spatial-media/blob/master/docs/vr180.md).
-* HLS:
- * Support PlayReady.
- * Add container format sniffing
- ([#2025](https://github.com/google/ExoPlayer/issues/2025)).
- * Support alternative `EXT-X-KEY` tags.
- * Support `EXT-X-INDEPENDENT-SEGMENTS` in the master playlist.
- * Support variable substitution
- ([#4422](https://github.com/google/ExoPlayer/issues/4422)).
- * Fix the bitrate being unset on primary track sample formats
- ([#3297](https://github.com/google/ExoPlayer/issues/3297)).
- * Make `HlsMediaSource.Factory` take a factory of trackers instead of a
- tracker instance ([#4814](https://github.com/google/ExoPlayer/issues/4814)).
-* DASH:
- * Support `messageData` attribute for in-manifest event streams.
- * Clip periods to their specified durations
- ([#4185](https://github.com/google/ExoPlayer/issues/4185)).
-* Improve seeking support for progressive streams:
- * Support seeking in MPEG-TS
- ([#966](https://github.com/google/ExoPlayer/issues/966)).
- * Support seeking in MPEG-PS
- ([#4476](https://github.com/google/ExoPlayer/issues/4476)).
- * Support approximate seeking in ADTS using a constant bitrate assumption
- ([#4548](https://github.com/google/ExoPlayer/issues/4548)). The
- `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` flag must be set on the extractor to
- enable this functionality.
- * Support approximate seeking in AMR using a constant bitrate assumption.
- The `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` flag must be set on the extractor
- to enable this functionality.
- * Add `DefaultExtractorsFactory.setConstantBitrateSeekingEnabled` to enable
- approximate seeking using a constant bitrate assumption on all extractors
- that support it.
-* Video:
- * Add callback to `VideoListener` to notify of surface size changes.
- * Improve performance when playing high frame-rate content, and when playing
- at greater than 1x speed
- ([#2777](https://github.com/google/ExoPlayer/issues/2777)).
- * Scale up the initial video decoder maximum input size so playlist
- transitions with small increases in maximum sample size do not require
- reinitialization ([#4510](https://github.com/google/ExoPlayer/issues/4510)).
- * Fix a bug where the player would not transition to the ended state when
- playing video in tunneled mode.
-* Audio:
- * Support attaching auxiliary audio effects to the `AudioTrack` via
- `Player.setAuxEffectInfo` and `Player.clearAuxEffectInfo`.
- * Support seamless adaptation while playing xHE-AAC streams.
- ([#4360](https://github.com/google/ExoPlayer/issues/4360)).
- * Increase `AudioTrack` buffer sizes to the theoretical maximum required for
- each encoding for passthrough playbacks
- ([#3803](https://github.com/google/ExoPlayer/issues/3803)).
- * WAV: Fix issue where white noise would be output at the end of playback
- ([#4724](https://github.com/google/ExoPlayer/issues/4724)).
- * MP3: Fix issue where streams would play twice on the SM-T530
- ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
-* Analytics:
- * Add callbacks to `DefaultDrmSessionEventListener` and `AnalyticsListener` to
- be notified of acquired and released DRM sessions.
- * Add uri field to `LoadEventInfo` in `MediaSourceEventListener` and
- `AnalyticsListener` callbacks. This uri is the redirected uri if redirection
- occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)).
- * Add response headers field to `LoadEventInfo` in `MediaSourceEventListener`
- and `AnalyticsListener` callbacks
- ([#4361](https://github.com/google/ExoPlayer/issues/4361) and
- [#4615](https://github.com/google/ExoPlayer/issues/4615)).
-* UI:
- * Add option to `PlayerView` to show buffering view when playWhenReady is
- false ([#4304](https://github.com/google/ExoPlayer/issues/4304)).
- * Allow any `Drawable` to be used as `PlayerView` default artwork.
-* ConcatenatingMediaSource:
- * Support lazy preparation of playlist media sources
- ([#3972](https://github.com/google/ExoPlayer/issues/3972)).
- * Support range removal with `removeMediaSourceRange` methods
- ([#4542](https://github.com/google/ExoPlayer/issues/4542)).
- * Support setting a new shuffle order with `setShuffleOrder`
- ([#4791](https://github.com/google/ExoPlayer/issues/4791)).
-* MPEG-TS: Support CEA-608/708 in H262
- ([#2565](https://github.com/google/ExoPlayer/issues/2565)).
-* Allow configuration of the back buffer in `DefaultLoadControl.Builder`
- ([#4857](https://github.com/google/ExoPlayer/issues/4857)).
-* Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when
- creating a `CacheDataSource`.
-* Provide additional information for adaptive track selection.
- `TrackSelection.updateSelectedTrack` has two new parameters for the current
- queue of media chunks and iterators for information about upcoming chunks.
-* Allow `MediaCodecSelector`s to return multiple compatible decoders for
- `MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that
- falls back to less preferred decoders like `MediaCodec.createDecoderByType`
- ([#273](https://github.com/google/ExoPlayer/issues/273)).
-* Enable gzip for requests made by `SingleSampleMediaSource`
- ([#4771](https://github.com/google/ExoPlayer/issues/4771)).
-* Fix bug reporting buffered position for multi-period windows, and add
- convenience methods `Player.getTotalBufferedDuration` and
- `Player.getContentBufferedDuration`
- ([#4023](https://github.com/google/ExoPlayer/issues/4023)).
-* Fix bug where transitions to clipped media sources would happen too early
- ([#4583](https://github.com/google/ExoPlayer/issues/4583)).
-* Fix bugs reporting events for multi-period media sources
- ([#4492](https://github.com/google/ExoPlayer/issues/4492) and
- [#4634](https://github.com/google/ExoPlayer/issues/4634)).
-* Fix issue where removing looping media from a playlist throws an exception
- ([#4871](https://github.com/google/ExoPlayer/issues/4871).
-* Fix issue where the preferred audio or text track would not be selected if
- mapped onto a secondary renderer of the corresponding type
- ([#4711](http://github.com/google/ExoPlayer/issues/4711)).
-* Fix issue where errors of upcoming playlist items are thrown too early
- ([#4661](https://github.com/google/ExoPlayer/issues/4661)).
-* Allow edit lists which do not start with a sync sample.
- ([#4774](https://github.com/google/ExoPlayer/issues/4774)).
-* Fix issue with audio discontinuities at period transitions, e.g. when
- looping ([#3829](https://github.com/google/ExoPlayer/issues/3829)).
-* Fix issue where `player.getCurrentTag()` throws an `IndexOutOfBoundsException`
- ([#4822](https://github.com/google/ExoPlayer/issues/4822)).
-* Fix bug preventing use of multiple key session support (`multiSession=true`)
- for non-Widevine `DefaultDrmSessionManager` instances
- ([#4834](https://github.com/google/ExoPlayer/issues/4834)).
-* Fix issue where audio and video would desynchronize when playing
- concatenations of gapless content
- ([#4559](https://github.com/google/ExoPlayer/issues/4559)).
-* IMA extension:
- * Refine the previous fix for empty ad groups to avoid discarding ad breaks
- unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030) and
- [#4280](https://github.com/google/ExoPlayer/issues/4280)).
- * Fix handling of empty postrolls
- ([#4681](https://github.com/google/ExoPlayer/issues/4681)).
- * Fix handling of postrolls with multiple ads
- ([#4710](https://github.com/google/ExoPlayer/issues/4710)).
-* MediaSession extension:
- * Add `MediaSessionConnector.setCustomErrorMessage` to support setting custom
- error messages.
- * Add `MediaMetadataProvider` to support setting custom metadata
- ([#3497](https://github.com/google/ExoPlayer/issues/3497)).
-* Cronet extension: Now distributed via jCenter.
-* FFmpeg extension: Support mu-law and A-law PCM.
-
-### 2.8.4 (2018-08-17) ###
-
-* IMA extension: Improve handling of consecutive empty ad groups
- ([#4030](https://github.com/google/ExoPlayer/issues/4030)),
- ([#4280](https://github.com/google/ExoPlayer/issues/4280)).
-
-### 2.8.3 (2018-07-23) ###
-
-* IMA extension:
- * Fix behavior when creating/releasing the player then releasing
- `ImaAdsLoader` ([#3879](https://github.com/google/ExoPlayer/issues/3879)).
- * Add support for setting slots for companion ads.
-* Captions:
- * TTML: Fix an issue with TTML using font size as % of cell resolution that
- makes `SubtitleView.setApplyEmbeddedFontSizes()` not work correctly.
- ([#4491](https://github.com/google/ExoPlayer/issues/4491)).
- * CEA-608: Improve handling of embedded styles
- ([#4321](https://github.com/google/ExoPlayer/issues/4321)).
-* DASH:
- * Exclude text streams from duration calculations
- ([#4029](https://github.com/google/ExoPlayer/issues/4029)).
- * Fix freezing when playing multi-period manifests with `EventStream`s
- ([#4492](https://github.com/google/ExoPlayer/issues/4492)).
-* DRM: Allow DrmInitData to carry a license server URL
- ([#3393](https://github.com/google/ExoPlayer/issues/3393)).
-* MPEG-TS: Fix bug preventing SCTE-35 cues from being output
- ([#4573](https://github.com/google/ExoPlayer/issues/4573)).
-* Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
- CommentFrame to InternalFrame for frames with gapless metadata in MP4.
-* Add `PlayerView.isControllerVisible`
- ([#4385](https://github.com/google/ExoPlayer/issues/4385)).
-* Fix issue playing DRM protected streams on Asus Zenfone 2
- ([#4403](https://github.com/google/ExoPlayer/issues/4413)).
-* Add support for multiple audio and video tracks in MPEG-PS streams
- ([#4406](https://github.com/google/ExoPlayer/issues/4406)).
-* Add workaround for track index mismatches between trex and tkhd boxes in
- fragmented MP4 files
- ([#4477](https://github.com/google/ExoPlayer/issues/4477)).
-* Add workaround for track index mismatches between tfhd and tkhd boxes in
- fragmented MP4 files
- ([#4083](https://github.com/google/ExoPlayer/issues/4083)).
-* Ignore all MP4 edit lists if one edit list couldn't be handled
- ([#4348](https://github.com/google/ExoPlayer/issues/4348)).
-* Fix issue when switching track selection from an embedded track to a primary
- track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)).
-* Fix accessibility class name for `DefaultTimeBar`
- ([#4611](https://github.com/google/ExoPlayer/issues/4611)).
-* Improved compatibility with FireOS devices.
-
-### 2.8.2 (2018-06-06) ###
-
-* IMA extension: Don't advertise support for video/mpeg ad media, as we don't
- have an extractor for this
- ([#4297](https://github.com/google/ExoPlayer/issues/4297)).
-* DASH: Fix playback getting stuck when playing representations that have both
- sidx atoms and non-zero presentationTimeOffset values.
-* HLS:
- * Allow injection of custom playlist trackers.
- * Fix adaptation in live playlists with EXT-X-PROGRAM-DATE-TIME tags.
-* Mitigate memory leaks when `MediaSource` loads are slow to cancel
- ([#4249](https://github.com/google/ExoPlayer/issues/4249)).
-* Fix inconsistent `Player.EventListener` invocations for recursive player state
- changes ([#4276](https://github.com/google/ExoPlayer/issues/4276)).
-* Fix `MediaCodec.native_setSurface` crash on Moto C
- ([#4315](https://github.com/google/ExoPlayer/issues/4315)).
-* Fix missing whitespace in CEA-608
- ([#3906](https://github.com/google/ExoPlayer/issues/3906)).
-* Fix crash downloading HLS media playlists
- ([#4396](https://github.com/google/ExoPlayer/issues/4396)).
-* Fix a bug where download cancellation was ignored
- ([#4403](https://github.com/google/ExoPlayer/issues/4403)).
-* Set `METADATA_KEY_TITLE` on media descriptions
- ([#4292](https://github.com/google/ExoPlayer/issues/4292)).
-* Allow apps to register custom MIME types
- ([#4264](https://github.com/google/ExoPlayer/issues/4264)).
-
-### 2.8.1 (2018-05-22) ###
-
-* HLS:
- * Fix playback of livestreams with EXT-X-PROGRAM-DATE-TIME tags
- ([#4239](https://github.com/google/ExoPlayer/issues/4239)).
- * Fix playback of clipped streams starting from non-keyframe positions
- ([#4241](https://github.com/google/ExoPlayer/issues/4241)).
-* OkHttp extension: Fix to correctly include response headers in thrown
- `InvalidResponseCodeException`s.
-* Add possibility to cancel `PlayerMessage`s.
-* UI:
- * Add `PlayerView.setKeepContentOnPlayerReset` to keep the currently displayed
- video frame or media artwork visible when the player is reset
- ([#2843](https://github.com/google/ExoPlayer/issues/2843)).
-* Fix crash when switching surface on Moto E(4)
- ([#4134](https://github.com/google/ExoPlayer/issues/4134)).
-* Fix a bug that could cause event listeners to be called with inconsistent
- information if an event listener interacted with the player
- ([#4262](https://github.com/google/ExoPlayer/issues/4262)).
-* Audio:
- * Fix extraction of PCM in MP4/MOV
- ([#4228](https://github.com/google/ExoPlayer/issues/4228)).
- * FLAC: Supports seeking for FLAC files without SEEKTABLE
- ([#1808](https://github.com/google/ExoPlayer/issues/1808)).
-* Captions:
- * TTML:
- * Fix a styling issue when there are multiple regions displayed at the same
- time that can make text size of each region much smaller than defined.
- * Fix an issue when the caption line has no text (empty line or only line
- break), and the line's background is still displayed.
- * Support TTML font size using % correctly (as percentage of document cell
- resolution).
-
-### 2.8.0 (2018-05-03) ###
-
-* Downloading:
- * Add `DownloadService`, `DownloadManager` and related classes
- ([#2643](https://github.com/google/ExoPlayer/issues/2643)). Information on
- using these components to download progressive formats can be found
- [here](https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95).
- To see how to download DASH, HLS and SmoothStreaming media, take a look at
- the app.
- * Updated main demo app to support downloading DASH, HLS, SmoothStreaming and
- progressive media.
-* MediaSources:
- * Allow reusing media sources after they have been released and
- also in parallel to allow adding them multiple times to a concatenation.
- ([#3498](https://github.com/google/ExoPlayer/issues/3498)).
- * Merged `DynamicConcatenatingMediaSource` into `ConcatenatingMediaSource` and
- deprecated `DynamicConcatenatingMediaSource`.
- * Allow clipping of child media sources where the period and window have a
- non-zero offset with `ClippingMediaSource`.
- * Allow adding and removing `MediaSourceEventListener`s to MediaSources after
- they have been created. Listening to events is now supported for all
- media sources including composite sources.
- * Added callbacks to `MediaSourceEventListener` to get notified when media
- periods are created, released and being read from.
- * Support live stream clipping with `ClippingMediaSource`.
- * Allow setting tags for all media sources in their factories. The tag of the
- current window can be retrieved with `Player.getCurrentTag`.
-* UI:
- * Add support for displaying error messages and a buffering spinner in
- `PlayerView`.
- * Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
- ([#3736](https://github.com/google/ExoPlayer/issues/3736)).
- * Add `PlayerNotificationManager` for displaying notifications reflecting the
- player state.
- * Add `TrackSelectionView` for selecting tracks with `DefaultTrackSelector`.
- * Add `TrackNameProvider` for converting track `Format`s to textual
- descriptions, and `DefaultTrackNameProvider` as a default implementation.
-* Track selection:
- * Reworked `MappingTrackSelector` and `DefaultTrackSelector`.
- * `DefaultTrackSelector.Parameters` now implements `Parcelable`.
- * Added UI components for track selection (see above).
-* Audio:
- * Support extracting data from AMR container formats, including both narrow
- and wide band ([#2527](https://github.com/google/ExoPlayer/issues/2527)).
- * FLAC:
- * Sniff FLAC files correctly if they have ID3 headers
- ([#4055](https://github.com/google/ExoPlayer/issues/4055)).
- * Supports FLAC files with high sample rate (176400 and 192000)
- ([#3769](https://github.com/google/ExoPlayer/issues/3769)).
- * Factor out `AudioTrack` position tracking from `DefaultAudioSink`.
- * Fix an issue where the playback position would pause just after playback
- begins, and poll the audio timestamp less frequently once it starts
- advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
- * Add an option to skip silent audio in `PlaybackParameters`
- ([#2635](https://github.com/google/ExoPlayer/issues/2635)).
- * Fix an issue where playback of TrueHD streams would get stuck after seeking
- due to not finding a syncframe
- ([#3845](https://github.com/google/ExoPlayer/issues/3845)).
- * Fix an issue with eac3-joc playback where a codec would fail to configure
- ([#4165](https://github.com/google/ExoPlayer/issues/4165)).
- * Handle non-empty end-of-stream buffers, to fix gapless playback of streams
- with encoder padding when the decoder returns a non-empty final buffer.
- * Allow trimming more than one sample when applying an elst audio edit via
- gapless playback info.
- * Allow overriding skipping/scaling with custom `AudioProcessor`s
- ([#3142](https://github.com/google/ExoPlayer/issues/3142)).
-* Caching:
- * Add release method to the `Cache` interface, and prevent multiple instances
- of `SimpleCache` using the same folder at the same time.
- * Cache redirect URLs
- ([#2360](https://github.com/google/ExoPlayer/issues/2360)).
-* DRM:
- * Allow multiple listeners for `DefaultDrmSessionManager`.
- * Pass `DrmSessionManager` to `ExoPlayerFactory` instead of `RendererFactory`.
- * Change minimum API requirement for CBC and pattern encryption from 24 to 25
- ([#4022](https://github.com/google/ExoPlayer/issues/4022)).
- * Fix handling of 307/308 redirects when making license requests
- ([#4108](https://github.com/google/ExoPlayer/issues/4108)).
-* HLS:
- * Fix playlist loading error propagation when the current selection does
- not include all of the playlist's variants.
- * Fix SAMPLE-AES-CENC and SAMPLE-AES-CTR EXT-X-KEY methods
- ([#4145](https://github.com/google/ExoPlayer/issues/4145)).
- * Preeptively declare an ID3 track in chunkless preparation
- ([#4016](https://github.com/google/ExoPlayer/issues/4016)).
- * Add support for multiple #EXT-X-MAP tags in a media playlist
- ([#4164](https://github.com/google/ExoPlayer/issues/4182)).
- * Fix seeking in live streams
- ([#4187](https://github.com/google/ExoPlayer/issues/4187)).
-* IMA extension:
- * Allow setting the ad media load timeout
- ([#3691](https://github.com/google/ExoPlayer/issues/3691)).
- * Expose ad load errors via `MediaSourceEventListener` on `AdsMediaSource`,
- and allow setting an ad event listener on `ImaAdsLoader`. Deprecate the
- `AdsMediaSource.EventListener`.
-* Add `AnalyticsListener` interface which can be registered in
- `SimpleExoPlayer` to receive detailed metadata for each ExoPlayer event.
-* Optimize seeking in FMP4 by enabling seeking to the nearest sync sample within
- a fragment. This benefits standalone FMP4 playbacks, DASH and SmoothStreaming.
-* Updated default max buffer length in `DefaultLoadControl`.
-* Fix ClearKey decryption error if the key contains a forward slash
- ([#4075](https://github.com/google/ExoPlayer/issues/4075)).
-* Fix crash when switching surface on Huawei P9 Lite
- ([#4084](https://github.com/google/ExoPlayer/issues/4084)), and Philips QM163E
- ([#4104](https://github.com/google/ExoPlayer/issues/4104)).
-* Support ZLIB compressed PGS subtitles.
-* Added `getPlaybackError` to `Player` interface.
-* Moved initial bitrate estimate from `AdaptiveTrackSelection` to
- `DefaultBandwidthMeter`.
-* Removed default renderer time offset of 60000000 from internal player. The
- actual renderer timestamp offset can be obtained by listening to
- `BaseRenderer.onStreamChanged`.
-* Added dependencies on checkerframework annotations for static code analysis.
-
-### 2.7.3 (2018-04-04) ###
-
-* Fix ProGuard configuration for Cast, IMA and OkHttp extensions.
-* Update OkHttp extension to depend on OkHttp 3.10.0.
-
-### 2.7.2 (2018-03-29) ###
-
-* Gradle: Upgrade Gradle version from 4.1 to 4.4 so it can work with Android
- Studio 3.1 ([#3708](https://github.com/google/ExoPlayer/issues/3708)).
-* Match codecs starting with "mp4a" to different Audio MimeTypes
- ([#3779](https://github.com/google/ExoPlayer/issues/3779)).
-* Fix ANR issue on Redmi 4X and Redmi Note 4
- ([#4006](https://github.com/google/ExoPlayer/issues/4006)).
-* Fix handling of zero padded strings when parsing Matroska streams
- ([#4010](https://github.com/google/ExoPlayer/issues/4010)).
-* Fix "Decoder input buffer too small" error when playing some FLAC streams.
-* MediaSession extension: Omit fast forward and rewind actions when media is not
- seekable ([#4001](https://github.com/google/ExoPlayer/issues/4001)).
-
-### 2.7.1 (2018-03-09) ###
-
-* Gradle: Replaced 'compile' (deprecated) with 'implementation' and
- 'api'. This may lead to build breakage for applications upgrading from
- previous version that rely on indirect dependencies of certain modules. In
- such cases, application developers need to add the missing dependency to
- their gradle file. You can read more about the new dependency configurations
- [here](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#new_configurations).
-* HlsMediaSource: Make HLS periods start at zero instead of the epoch.
- Applications that rely on HLS timelines having a period starting at
- the epoch will need to update their handling of HLS timelines. The program
- date time is still available via the informational
- `Timeline.Window.windowStartTimeMs` field
- ([#3865](https://github.com/google/ExoPlayer/issues/3865),
- [#3888](https://github.com/google/ExoPlayer/issues/3888)).
-* Enable seeking in MP4 streams where duration is set incorrectly in the track
- header ([#3926](https://github.com/google/ExoPlayer/issues/3926)).
-* Video: Force rendering a frame periodically in `MediaCodecVideoRenderer` and
- `LibvpxVideoRenderer`, even if it is late.
-
-### 2.7.0 (2018-02-19) ###
-
-* Player interface:
- * Add optional parameter to `stop` to reset the player when stopping.
- * Add a reason to `EventListener.onTimelineChanged` to distinguish between
- initial preparation, reset and dynamic updates.
- * Add `Player.DISCONTINUITY_REASON_AD_INSERTION` to the possible reasons
- reported in `Eventlistener.onPositionDiscontinuity` to distinguish
- transitions to and from ads within one period from transitions between
- periods.
- * Replaced `ExoPlayer.sendMessages` with `ExoPlayer.createMessage` to allow
- more customization of the message. Now supports setting a message delivery
- playback position and/or a delivery handler
- ([#2189](https://github.com/google/ExoPlayer/issues/2189)).
- * Add `Player.VideoComponent`, `Player.TextComponent` and
- `Player.MetadataComponent` interfaces that define optional video, text and
- metadata output functionality. New `getVideoComponent`, `getTextComponent`
- and `getMetadataComponent` methods provide access to this functionality.
-* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are
- performed. The `SeekParameters` class contains defaults for exact seeking and
- seeking to the closest sync points before, either side or after specified seek
- positions. `SeekParameters` are not currently supported when playing HLS
- streams.
-* DefaultTrackSelector:
- * Replace `DefaultTrackSelector.Parameters` copy methods with a builder.
- * Support disabling of individual text track selection flags.
-* Buffering:
- * Allow a back-buffer of media to be retained behind the current playback
- position, for fast backward seeking. The back-buffer can be configured by
- custom `LoadControl` implementations.
- * Add ability for `SequenceableLoader` to re-evaluate its buffer and discard
- buffered media so that it can be re-buffered in a different quality.
- * Allow more flexible loading strategy when playing media containing multiple
- sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
- factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`,
- `SsMediaSource.Factory`, and `MergingMediaSource`.
- * Play out existing buffer before retrying for progressive live streams
- ([#1606](https://github.com/google/ExoPlayer/issues/1606)).
-* UI:
- * Generalized player and control views to allow them to bind with any
- `Player`, and renamed them to `PlayerView` and `PlayerControlView`
- respectively.
- * Made `PlayerView` automatically apply video rotation when configured to use
- `TextureView` ([#91](https://github.com/google/ExoPlayer/issues/91)).
- * Made `PlayerView` play button behave correctly when the player is ended
- ([#3689](https://github.com/google/ExoPlayer/issues/3689)), and call a
- `PlaybackPreparer` when the player is idle.
-* DRM: Optimistically attempt playback of DRM protected content that does not
- declare scheme specific init data in the manifest. If playback of clear
- samples without keys is allowed, delay DRM session error propagation until
- keys are actually needed
- ([#3630](https://github.com/google/ExoPlayer/issues/3630)).
-* DASH:
- * Support in-band Emsg events targeting the player with scheme id
- `urn:mpeg:dash:event:2012` and scheme values "1", "2" and "3".
- * Support EventStream elements in DASH manifests.
-* HLS:
- * Add opt-in support for chunkless preparation in HLS. This allows an
- HLS source to finish preparation without downloading any chunks, which can
- significantly reduce initial buffering time
- ([#3149](https://github.com/google/ExoPlayer/issues/3149)). More details
- can be found
- [here](https://medium.com/google-exoplayer/faster-hls-preparation-f6611aa15ea6).
- * Fail if unable to sync with the Transport Stream, rather than entering
- stuck in an indefinite buffering state.
- * Fix mime type propagation
- ([#3653](https://github.com/google/ExoPlayer/issues/3653)).
- * Fix ID3 context reuse across segment format changes
- ([#3622](https://github.com/google/ExoPlayer/issues/3622)).
- * Use long for media sequence numbers
- ([#3747](https://github.com/google/ExoPlayer/issues/3747))
- * Add initial support for the EXT-X-GAP tag.
-* Audio:
- * Support TrueHD passthrough for rechunked samples in Matroska files
- ([#2147](https://github.com/google/ExoPlayer/issues/2147)).
- * Support resampling 24-bit and 32-bit integer to 32-bit float for high
- resolution output in `DefaultAudioSink`
- ([#3635](https://github.com/google/ExoPlayer/pull/3635)).
-* Captions:
- * Basic support for PGS subtitles
- ([#3008](https://github.com/google/ExoPlayer/issues/3008)).
- * Fix handling of CEA-608 captions where multiple buffers have the same
- presentation timestamp
- ([#3782](https://github.com/google/ExoPlayer/issues/3782)).
-* Caching:
- * Fix cache corruption issue
- ([#3762](https://github.com/google/ExoPlayer/issues/3762)).
- * Implement periodic check in `CacheDataSource` to see whether it's possible
- to switch to reading/writing the cache having initially bypassed it.
-* IMA extension:
- * Fix the player getting stuck when an ad group fails to load
- ([#3584](https://github.com/google/ExoPlayer/issues/3584)).
- * Work around loadAd not being called beore the LOADED AdEvent arrives
- ([#3552](https://github.com/google/ExoPlayer/issues/3552)).
- * Handle asset mismatch errors
- ([#3801](https://github.com/google/ExoPlayer/issues/3801)).
- * Add support for playing non-Extractor content MediaSources in
- the IMA demo app
- ([#3676](https://github.com/google/ExoPlayer/issues/3676)).
- * Fix handling of ad tags where ad groups are out of order
- ([#3716](https://github.com/google/ExoPlayer/issues/3716)).
- * Fix handling of ad tags with only preroll/postroll ad groups
- ([#3715](https://github.com/google/ExoPlayer/issues/3715)).
- * Propagate ad media preparation errors to IMA so that the ads can be
- skipped.
- * Handle exceptions in IMA callbacks so that can be logged less verbosely.
-* New Cast extension. Simplifies toggling between local and Cast playbacks.
-* `EventLogger` moved from the demo app into the core library.
-* Fix ANR issue on the Huawei P8 Lite, Huawei Y6II, Moto C+, Meizu M5C,
- Lenovo K4 Note and Sony Xperia E5.
- ([#3724](https://github.com/google/ExoPlayer/issues/3724),
- [#3835](https://github.com/google/ExoPlayer/issues/3835)).
-* Fix potential NPE when removing media sources from a
- DynamicConcatenatingMediaSource
- ([#3796](https://github.com/google/ExoPlayer/issues/3796)).
-* Check `sys.display-size` on Philips ATVs
- ([#3807](https://github.com/google/ExoPlayer/issues/3807)).
-* Release `Extractor`s on the loading thread to avoid potentially leaking
- resources when the playback thread has quit by the time the loading task has
- completed.
-* ID3: Better handle malformed ID3 data
- ([#3792](https://github.com/google/ExoPlayer/issues/3792).
-* Support 14-bit mode and little endianness in DTS PES packets
- ([#3340](https://github.com/google/ExoPlayer/issues/3340)).
-* Demo app: Add ability to download not DRM protected content.
-
-### 2.6.1 (2017-12-15) ###
-
-* Add factories to `ExtractorMediaSource`, `HlsMediaSource`, `SsMediaSource`,
- `DashMediaSource` and `SingleSampleMediaSource`.
-* Use the same listener `MediaSourceEventListener` for all MediaSource
- implementations.
-* IMA extension:
- * Support non-ExtractorMediaSource ads
- ([#3302](https://github.com/google/ExoPlayer/issues/3302)).
- * Skip ads before the ad preceding the player's initial seek position
- ([#3527](https://github.com/google/ExoPlayer/issues/3527)).
- * Fix ad loading when there is no preroll.
- * Add an option to turn off hiding controls during ad playback
- ([#3532](https://github.com/google/ExoPlayer/issues/3532)).
- * Support specifying an ads response instead of an ad tag
- ([#3548](https://github.com/google/ExoPlayer/issues/3548)).
- * Support overriding the ad load timeout
- ([#3556](https://github.com/google/ExoPlayer/issues/3556)).
-* DASH: Support time zone designators in ISO8601 UTCTiming elements
- ([#3524](https://github.com/google/ExoPlayer/issues/3524)).
-* Audio:
- * Support 32-bit PCM float output from `DefaultAudioSink`, and add an option
- to use this with `FfmpegAudioRenderer`.
- * Add support for extracting 32-bit WAVE files
- ([#3379](https://github.com/google/ExoPlayer/issues/3379)).
- * Support extraction and decoding of Dolby Atmos
- ([#2465](https://github.com/google/ExoPlayer/issues/2465)).
- * Fix handling of playback parameter changes while paused when followed by a
- seek.
-* SimpleExoPlayer: Allow multiple audio and video debug listeners.
-* DefaultTrackSelector: Support undefined language text track selection when the
- preferred language is not available
- ([#2980](https://github.com/google/ExoPlayer/issues/2980)).
-* Add options to `DefaultLoadControl` to set maximum buffer size in bytes and
- to choose whether size or time constraints are prioritized.
-* Use surfaceless context for secure `DummySurface`, if available
- ([#3558](https://github.com/google/ExoPlayer/issues/3558)).
-* FLV: Fix playback of live streams that do not contain an audio track
- ([#3188](https://github.com/google/ExoPlayer/issues/3188)).
-* CEA-608: Fix handling of row count changes in roll-up mode
- ([#3513](https://github.com/google/ExoPlayer/issues/3513)).
-* Prevent period transitions when seeking to the end of a period when paused
- ([#2439](https://github.com/google/ExoPlayer/issues/2439)).
-
-### 2.6.0 (2017-11-03) ###
-
-* Removed "r" prefix from versions. This release is "2.6.0", not "r2.6.0".
-* New `Player.DefaultEventListener` abstract class can be extended to avoid
- having to implement all methods defined by `Player.EventListener`.
-* Added a reason to `EventListener.onPositionDiscontinuity`
- ([#3252](https://github.com/google/ExoPlayer/issues/3252)).
-* New `setShuffleModeEnabled` method for enabling shuffled playback.
-* SimpleExoPlayer: Support for multiple video, text and metadata outputs.
-* Support for `Renderer`s that don't consume any media
- ([#3212](https://github.com/google/ExoPlayer/issues/3212)).
-* Fix reporting of internal position discontinuities via
- `Player.onPositionDiscontinuity`. `DISCONTINUITY_REASON_SEEK_ADJUSTMENT` is
- added to disambiguate position adjustments during seeks from other types of
- internal position discontinuity.
-* Fix potential `IndexOutOfBoundsException` when calling `ExoPlayer.getDuration`
- ([#3362](https://github.com/google/ExoPlayer/issues/3362)).
-* Fix playbacks involving looping, concatenation and ads getting stuck when
- media contains tracks with uneven durations
- ([#1874](https://github.com/google/ExoPlayer/issues/1874)).
-* Fix issue with `ContentDataSource` when reading from certain `ContentProvider`
- implementations ([#3426](https://github.com/google/ExoPlayer/issues/3426)).
-* Better playback experience when the video decoder cannot keep up, by skipping
- to key-frames. This is particularly relevant for variable speed playbacks.
-* Allow `SingleSampleMediaSource` to suppress load errors
- ([#3140](https://github.com/google/ExoPlayer/issues/3140)).
-* `DynamicConcatenatingMediaSource`: Allow specifying a callback to be invoked
- after a dynamic playlist modification has been applied
- ([#3407](https://github.com/google/ExoPlayer/issues/3407)).
-* Audio: New `AudioSink` interface allows customization of audio output path.
-* Offline: Added `Downloader` implementations for DASH, HLS, SmoothStreaming
- and progressive streams.
-* Track selection:
- * Fixed adaptive track selection logic for live playbacks
- ([#3017](https://github.com/google/ExoPlayer/issues/3017)).
- * Added ability to select the lowest bitrate tracks.
-* DASH:
- * Don't crash when a malformed or unexpected manifest update occurs
- ([#2795](https://github.com/google/ExoPlayer/issues/2795)).
-* HLS:
- * Support for Widevine protected FMP4 variants.
- * Support CEA-608 in FMP4 variants.
- * Support extractor injection
- ([#2748](https://github.com/google/ExoPlayer/issues/2748)).
-* DRM:
- * Improved compatibility with ClearKey content
- ([#3138](https://github.com/google/ExoPlayer/issues/3138)).
- * Support multiple PSSH boxes of the same type.
- * Retry initial provisioning and key requests if they fail
- * Fix incorrect parsing of non-CENC sinf boxes.
-* IMA extension:
- * Expose `AdsLoader` via getter
- ([#3322](https://github.com/google/ExoPlayer/issues/3322)).
- * Handle `setPlayWhenReady` calls during ad playbacks
- ([#3303](https://github.com/google/ExoPlayer/issues/3303)).
- * Ignore seeks if an ad is playing
- ([#3309](https://github.com/google/ExoPlayer/issues/3309)).
- * Improve robustness of `ImaAdsLoader` in case content is not paused between
- content to ad transitions
- ([#3430](https://github.com/google/ExoPlayer/issues/3430)).
-* UI:
- * Allow specifying a `Drawable` for the `TimeBar` scrubber
- ([#3337](https://github.com/google/ExoPlayer/issues/3337)).
- * Allow multiple listeners on `TimeBar`
- ([#3406](https://github.com/google/ExoPlayer/issues/3406)).
-* New Leanback extension: Simplifies binding Exoplayer to Leanback UI
- components.
-* Unit tests moved to Robolectric.
-* Misc bugfixes.
-
-### r2.5.4 (2017-10-19) ###
-
-* Remove unnecessary media playlist fetches during playback of live HLS streams.
-* Add the ability to inject a HLS playlist parser through `HlsMediaSource`.
-* Fix potential `IndexOutOfBoundsException` when using `ImaMediaSource`
- ([#3334](https://github.com/google/ExoPlayer/issues/3334)).
-* Fix an issue parsing MP4 content containing non-CENC sinf boxes.
-* Fix memory leak when seeking with repeated periods.
-* Fix playback position when `ExoPlayer.prepare` is called with `resetPosition`
- set to false.
-* Ignore MP4 edit lists that seem invalid
- ([#3351](https://github.com/google/ExoPlayer/issues/3351)).
-* Add extractor flag for ignoring all MP4 edit lists
- ([#3358](https://github.com/google/ExoPlayer/issues/3358)).
-* Improve extensibility by exposing public constructors for
- `FrameworkMediaCrypto` and by making `DefaultDashChunkSource.getNextChunk`
- non-final.
+* Remove unnecessary media playlist fetches during playback of live HLS
+ streams.
+* Add the ability to inject a HLS playlist parser through `HlsMediaSource`.
+* Fix potential `IndexOutOfBoundsException` when using `ImaMediaSource`
+ ([#3334](https://github.com/google/ExoPlayer/issues/3334)).
+* Fix an issue parsing MP4 content containing non-CENC sinf boxes.
+* Fix memory leak when seeking with repeated periods.
+* Fix playback position when `ExoPlayer.prepare` is called with
+ `resetPosition` set to false.
+* Ignore MP4 edit lists that seem invalid
+ ([#3351](https://github.com/google/ExoPlayer/issues/3351)).
+* Add extractor flag for ignoring all MP4 edit lists
+ ([#3358](https://github.com/google/ExoPlayer/issues/3358)).
+* Improve extensibility by exposing public constructors for
+ `FrameworkMediaCrypto` and by making `DefaultDashChunkSource.getNextChunk`
+ non-final.
-### r2.5.3 (2017-09-20) ###
+### r2.5.3 (2017-09-20)
-* IMA extension: Support skipping of skippable ads on AndroidTV and other
- non-touch devices ([#3258](https://github.com/google/ExoPlayer/issues/3258)).
-* HLS: Fix broken WebVTT captions when PTS wraps around
- ([#2928](https://github.com/google/ExoPlayer/issues/2928)).
-* Captions: Fix issues rendering CEA-608 captions
- ([#3250](https://github.com/google/ExoPlayer/issues/3250)).
-* Workaround broken AAC decoders on Galaxy S6
- ([#3249](https://github.com/google/ExoPlayer/issues/3249)).
-* Caching: Fix infinite loop when cache eviction fails
- ([#3260](https://github.com/google/ExoPlayer/issues/3260)).
-* Caching: Force use of BouncyCastle on JellyBean to fix decryption issue
- ([#2755](https://github.com/google/ExoPlayer/issues/2755)).
+* IMA extension: Support skipping of skippable ads on AndroidTV and other
+ non-touch devices
+ ([#3258](https://github.com/google/ExoPlayer/issues/3258)).
+* HLS: Fix broken WebVTT captions when PTS wraps around
+ ([#2928](https://github.com/google/ExoPlayer/issues/2928)).
+* Captions: Fix issues rendering CEA-608 captions
+ ([#3250](https://github.com/google/ExoPlayer/issues/3250)).
+* Workaround broken AAC decoders on Galaxy S6
+ ([#3249](https://github.com/google/ExoPlayer/issues/3249)).
+* Caching: Fix infinite loop when cache eviction fails
+ ([#3260](https://github.com/google/ExoPlayer/issues/3260)).
+* Caching: Force use of BouncyCastle on JellyBean to fix decryption issue
+ ([#2755](https://github.com/google/ExoPlayer/issues/2755)).
-### r2.5.2 (2017-09-11) ###
+### r2.5.2 (2017-09-11)
-* IMA extension: Fix issue where ad playback could end prematurely for some
- content types ([#3180](https://github.com/google/ExoPlayer/issues/3180)).
-* RTMP extension: Fix SIGABRT on fast RTMP stream restart
- ([#3156](https://github.com/google/ExoPlayer/issues/3156)).
-* UI: Allow app to manually specify ad markers
- ([#3184](https://github.com/google/ExoPlayer/issues/3184)).
-* DASH: Expose segment indices to subclasses of DefaultDashChunkSource
- ([#3037](https://github.com/google/ExoPlayer/issues/3037)).
-* Captions: Added robustness against malformed WebVTT captions
- ([#3228](https://github.com/google/ExoPlayer/issues/3228)).
-* DRM: Support forcing a specific license URL.
-* Fix playback error when seeking in media loaded through content:// URIs
- ([#3216](https://github.com/google/ExoPlayer/issues/3216)).
-* Fix issue playing MP4s in which the last atom specifies a size of zero
- ([#3191](https://github.com/google/ExoPlayer/issues/3191)).
-* Workaround playback failures on some Xiaomi devices
- ([#3171](https://github.com/google/ExoPlayer/issues/3171)).
-* Workaround SIGSEGV issue on some devices when setting and swapping surface for
- secure playbacks ([#3215](https://github.com/google/ExoPlayer/issues/3215)).
-* Workaround for Nexus 7 issue when swapping output surface
- ([#3236](https://github.com/google/ExoPlayer/issues/3236)).
-* Workaround for SimpleExoPlayerView's surface not being hidden properly
- ([#3160](https://github.com/google/ExoPlayer/issues/3160)).
+* IMA extension: Fix issue where ad playback could end prematurely for some
+ content types ([#3180](https://github.com/google/ExoPlayer/issues/3180)).
+* RTMP extension: Fix SIGABRT on fast RTMP stream restart
+ ([#3156](https://github.com/google/ExoPlayer/issues/3156)).
+* UI: Allow app to manually specify ad markers
+ ([#3184](https://github.com/google/ExoPlayer/issues/3184)).
+* DASH: Expose segment indices to subclasses of DefaultDashChunkSource
+ ([#3037](https://github.com/google/ExoPlayer/issues/3037)).
+* Captions: Added robustness against malformed WebVTT captions
+ ([#3228](https://github.com/google/ExoPlayer/issues/3228)).
+* DRM: Support forcing a specific license URL.
+* Fix playback error when seeking in media loaded through content:// URIs
+ ([#3216](https://github.com/google/ExoPlayer/issues/3216)).
+* Fix issue playing MP4s in which the last atom specifies a size of zero
+ ([#3191](https://github.com/google/ExoPlayer/issues/3191)).
+* Workaround playback failures on some Xiaomi devices
+ ([#3171](https://github.com/google/ExoPlayer/issues/3171)).
+* Workaround SIGSEGV issue on some devices when setting and swapping surface
+ for secure playbacks
+ ([#3215](https://github.com/google/ExoPlayer/issues/3215)).
+* Workaround for Nexus 7 issue when swapping output surface
+ ([#3236](https://github.com/google/ExoPlayer/issues/3236)).
+* Workaround for SimpleExoPlayerView's surface not being hidden properly
+ ([#3160](https://github.com/google/ExoPlayer/issues/3160)).
-### r2.5.1 (2017-08-08) ###
+### r2.5.1 (2017-08-08)
-* Fix an issue that could cause the reported playback position to stop advancing
- in some cases.
-* Fix an issue where a Surface could be released whilst still in use by the
- player.
+* Fix an issue that could cause the reported playback position to stop
+ advancing in some cases.
+* Fix an issue where a Surface could be released whilst still in use by the
+ player.
-### r2.5.0 (2017-08-07) ###
+### r2.5.0 (2017-08-07)
-* IMA extension: Wraps the Google Interactive Media Ads (IMA) SDK to provide an
- easy and seamless way of incorporating display ads into ExoPlayer playbacks.
- You can read more about the IMA extension
- [here](https://medium.com/google-exoplayer/playing-ads-with-exoplayer-and-ima-868dfd767ea).
-* MediaSession extension: Provides an easy way to connect ExoPlayer with
- MediaSessionCompat in the Android Support Library.
-* RTMP extension: An extension for playing streams over RTMP.
-* Build: Made it easier for application developers to depend on a local checkout
- of ExoPlayer. You can learn how to do this
- [here](https://medium.com/google-exoplayer/howto-2-depend-on-a-local-checkout-of-exoplayer-bcd7f8531720).
-* Core playback improvements:
- * Eliminated re-buffering when changing audio and text track selections during
- playback of progressive streams
- ([#2926](https://github.com/google/ExoPlayer/issues/2926)).
- * New DynamicConcatenatingMediaSource class to support playback of dynamic
- playlists.
- * New ExoPlayer.setRepeatMode method for dynamic toggling of repeat mode
- during playback. Use of setRepeatMode should be preferred to
- LoopingMediaSource for most looping use cases. You can read more about
- setRepeatMode
- [here](https://medium.com/google-exoplayer/repeat-modes-in-exoplayer-19dd85f036d3).
- * Eliminated jank when switching video playback from one Surface to another on
- API level 23+ for unencrypted content, and on devices that support the
- EGL_EXT_protected_content OpenGL extension for protected content
- ([#677](https://github.com/google/ExoPlayer/issues/677)).
- * Enabled ExoPlayer instantiation on background threads without Loopers.
- Events from such players are delivered on the application's main thread.
-* HLS improvements:
- * Optimized adaptive switches for playlists that specify the
- EXT-X-INDEPENDENT-SEGMENTS tag.
- * Optimized in-buffer seeking
- ([#551](https://github.com/google/ExoPlayer/issues/551)).
- * Eliminated re-buffering when changing audio and text track selections during
- playback, provided the new selection does not require switching to different
- renditions ([#2718](https://github.com/google/ExoPlayer/issues/2718)).
- * Exposed all media playlist tags in ExoPlayer's MediaPlaylist object.
-* DASH: Support for seamless switching across streams in different AdaptationSet
- elements ([#2431](https://github.com/google/ExoPlayer/issues/2431)).
-* DRM: Support for additional crypto schemes (cbc1, cbcs and cens) on
- API level 24+ ([#1989](https://github.com/google/ExoPlayer/issues/1989)).
-* Captions: Initial support for SSA/ASS subtitles
- ([#889](https://github.com/google/ExoPlayer/issues/889)).
-* AndroidTV: Fixed issue where tunneled video playback would not start on some
- devices ([#2985](https://github.com/google/ExoPlayer/issues/2985)).
-* MPEG-TS: Fixed segmentation issue when parsing H262
- ([#2891](https://github.com/google/ExoPlayer/issues/2891)).
-* Cronet extension: Support for a user-defined fallback if Cronet library is not
- present.
-* Fix buffer too small IllegalStateException issue affecting some composite
- media playbacks ([#2900](https://github.com/google/ExoPlayer/issues/2900)).
-* Misc bugfixes.
+* IMA extension: Wraps the Google Interactive Media Ads (IMA) SDK to provide
+ an easy and seamless way of incorporating display ads into ExoPlayer
+ playbacks. You can read more about the IMA extension
+ [here](https://medium.com/google-exoplayer/playing-ads-with-exoplayer-and-ima-868dfd767ea).
+* MediaSession extension: Provides an easy way to connect ExoPlayer with
+ MediaSessionCompat in the Android Support Library.
+* RTMP extension: An extension for playing streams over RTMP.
+* Build: Made it easier for application developers to depend on a local
+ checkout of ExoPlayer. You can learn how to do this
+ [here](https://medium.com/google-exoplayer/howto-2-depend-on-a-local-checkout-of-exoplayer-bcd7f8531720).
+* Core playback improvements:
+ * Eliminated re-buffering when changing audio and text track selections
+ during playback of progressive streams
+ ([#2926](https://github.com/google/ExoPlayer/issues/2926)).
+ * New DynamicConcatenatingMediaSource class to support playback of dynamic
+ playlists.
+ * New ExoPlayer.setRepeatMode method for dynamic toggling of repeat mode
+ during playback. Use of setRepeatMode should be preferred to
+ LoopingMediaSource for most looping use cases. You can read more about
+ setRepeatMode
+ [here](https://medium.com/google-exoplayer/repeat-modes-in-exoplayer-19dd85f036d3).
+ * Eliminated jank when switching video playback from one Surface to
+ another on API level 23+ for unencrypted content, and on devices that
+ support the EGL_EXT_protected_content OpenGL extension for protected
+ content ([#677](https://github.com/google/ExoPlayer/issues/677)).
+ * Enabled ExoPlayer instantiation on background threads without Loopers.
+ Events from such players are delivered on the application's main thread.
+* HLS improvements:
+ * Optimized adaptive switches for playlists that specify the
+ EXT-X-INDEPENDENT-SEGMENTS tag.
+ * Optimized in-buffer seeking
+ ([#551](https://github.com/google/ExoPlayer/issues/551)).
+ * Eliminated re-buffering when changing audio and text track selections
+ during playback, provided the new selection does not require switching
+ to different renditions
+ ([#2718](https://github.com/google/ExoPlayer/issues/2718)).
+ * Exposed all media playlist tags in ExoPlayer's MediaPlaylist object.
+* DASH: Support for seamless switching across streams in different
+ AdaptationSet elements
+ ([#2431](https://github.com/google/ExoPlayer/issues/2431)).
+* DRM: Support for additional crypto schemes (cbc1, cbcs and cens) on API
+ level 24+ ([#1989](https://github.com/google/ExoPlayer/issues/1989)).
+* Captions: Initial support for SSA/ASS subtitles
+ ([#889](https://github.com/google/ExoPlayer/issues/889)).
+* AndroidTV: Fixed issue where tunneled video playback would not start on some
+ devices ([#2985](https://github.com/google/ExoPlayer/issues/2985)).
+* MPEG-TS: Fixed segmentation issue when parsing H262
+ ([#2891](https://github.com/google/ExoPlayer/issues/2891)).
+* Cronet extension: Support for a user-defined fallback if Cronet library is
+ not present.
+* Fix buffer too small IllegalStateException issue affecting some composite
+ media playbacks ([#2900](https://github.com/google/ExoPlayer/issues/2900)).
+* Misc bugfixes.
-### r2.4.4 (2017-07-19) ###
+### r2.4.4 (2017-07-19)
-* HLS/MPEG-TS: Some initial optimizations of MPEG-TS extractor performance
- ([#3040](https://github.com/google/ExoPlayer/issues/3040)).
-* HLS: Fix propagation of format identifier for CEA-608
- ([#3033](https://github.com/google/ExoPlayer/issues/3033)).
-* HLS: Detect playlist stuck and reset conditions
- ([#2872](https://github.com/google/ExoPlayer/issues/2872)).
-* Video: Fix video dimension reporting on some devices
- ([#3007](https://github.com/google/ExoPlayer/issues/3007)).
+* HLS/MPEG-TS: Some initial optimizations of MPEG-TS extractor performance
+ ([#3040](https://github.com/google/ExoPlayer/issues/3040)).
+* HLS: Fix propagation of format identifier for CEA-608
+ ([#3033](https://github.com/google/ExoPlayer/issues/3033)).
+* HLS: Detect playlist stuck and reset conditions
+ ([#2872](https://github.com/google/ExoPlayer/issues/2872)).
+* Video: Fix video dimension reporting on some devices
+ ([#3007](https://github.com/google/ExoPlayer/issues/3007)).
-### r2.4.3 (2017-06-30) ###
+### r2.4.3 (2017-06-30)
-* Audio: Workaround custom audio decoders misreporting their maximum supported
- channel counts ([#2940](https://github.com/google/ExoPlayer/issues/2940)).
-* Audio: Workaround for broken MediaTek raw decoder on some devices
- ([#2873](https://github.com/google/ExoPlayer/issues/2873)).
-* Captions: Fix TTML captions appearing at the top of the screen
- ([#2953](https://github.com/google/ExoPlayer/issues/2953)).
-* Captions: Fix handling of some DVB subtitles
- ([#2957](https://github.com/google/ExoPlayer/issues/2957)).
-* Track selection: Fix setSelectionOverride(index, tracks, null)
- ([#2988](https://github.com/google/ExoPlayer/issues/2988)).
-* GVR extension: Add support for mono input
- ([#2710](https://github.com/google/ExoPlayer/issues/2710)).
-* FLAC extension: Fix failing build
- ([#2977](https://github.com/google/ExoPlayer/pull/2977)).
-* Misc bugfixes.
+* Audio: Workaround custom audio decoders misreporting their maximum supported
+ channel counts ([#2940](https://github.com/google/ExoPlayer/issues/2940)).
+* Audio: Workaround for broken MediaTek raw decoder on some devices
+ ([#2873](https://github.com/google/ExoPlayer/issues/2873)).
+* Captions: Fix TTML captions appearing at the top of the screen
+ ([#2953](https://github.com/google/ExoPlayer/issues/2953)).
+* Captions: Fix handling of some DVB subtitles
+ ([#2957](https://github.com/google/ExoPlayer/issues/2957)).
+* Track selection: Fix setSelectionOverride(index, tracks, null)
+ ([#2988](https://github.com/google/ExoPlayer/issues/2988)).
+* GVR extension: Add support for mono input
+ ([#2710](https://github.com/google/ExoPlayer/issues/2710)).
+* FLAC extension: Fix failing build
+ ([#2977](https://github.com/google/ExoPlayer/pull/2977)).
+* Misc bugfixes.
-### r2.4.2 (2017-06-06) ###
+### r2.4.2 (2017-06-06)
-* Stability: Work around Nexus 10 reboot when playing certain content
- ([#2806](https://github.com/google/ExoPlayer/issues/2806)).
-* MP3: Correctly treat MP3s with INFO headers as constant bitrate
- ([#2895](https://github.com/google/ExoPlayer/issues/2895)).
-* HLS: Use average rather than peak bandwidth when available
- ([#2863](https://github.com/google/ExoPlayer/issues/2863)).
-* SmoothStreaming: Fix timeline for live streams
- ([#2760](https://github.com/google/ExoPlayer/issues/2760)).
-* UI: Fix DefaultTimeBar invalidation
- ([#2871](https://github.com/google/ExoPlayer/issues/2871)).
-* Misc bugfixes.
+* Stability: Work around Nexus 10 reboot when playing certain content
+ ([#2806](https://github.com/google/ExoPlayer/issues/2806)).
+* MP3: Correctly treat MP3s with INFO headers as constant bitrate
+ ([#2895](https://github.com/google/ExoPlayer/issues/2895)).
+* HLS: Use average rather than peak bandwidth when available
+ ([#2863](https://github.com/google/ExoPlayer/issues/2863)).
+* SmoothStreaming: Fix timeline for live streams
+ ([#2760](https://github.com/google/ExoPlayer/issues/2760)).
+* UI: Fix DefaultTimeBar invalidation
+ ([#2871](https://github.com/google/ExoPlayer/issues/2871)).
+* Misc bugfixes.
-### r2.4.1 (2017-05-23) ###
+### r2.4.1 (2017-05-23)
-* Stability: Avoid OutOfMemoryError in extractors when parsing malformed media
- ([#2780](https://github.com/google/ExoPlayer/issues/2780)).
-* Stability: Avoid native crash on Galaxy Nexus. Avoid unnecessarily large codec
- input buffer allocations on all devices
- ([#2607](https://github.com/google/ExoPlayer/issues/2607)).
-* Variable speed playback: Fix interpolation for rate/pitch adjustment
- ([#2774](https://github.com/google/ExoPlayer/issues/2774)).
-* HLS: Include EXT-X-DATERANGE tags in HlsMediaPlaylist.
-* HLS: Don't expose CEA-608 track if CLOSED-CAPTIONS=NONE
- ([#2743](https://github.com/google/ExoPlayer/issues/2743)).
-* HLS: Correctly propagate errors loading the media playlist
- ([#2623](https://github.com/google/ExoPlayer/issues/2623)).
-* UI: DefaultTimeBar enhancements and bug fixes
- ([#2740](https://github.com/google/ExoPlayer/issues/2740)).
-* Ogg: Fix failure to play some Ogg files
- ([#2782](https://github.com/google/ExoPlayer/issues/2782)).
-* Captions: Don't select text tack with no language by default.
-* Captions: TTML positioning fixes
- ([#2824](https://github.com/google/ExoPlayer/issues/2824)).
-* Misc bugfixes.
+* Stability: Avoid OutOfMemoryError in extractors when parsing malformed media
+ ([#2780](https://github.com/google/ExoPlayer/issues/2780)).
+* Stability: Avoid native crash on Galaxy Nexus. Avoid unnecessarily large
+ codec input buffer allocations on all devices
+ ([#2607](https://github.com/google/ExoPlayer/issues/2607)).
+* Variable speed playback: Fix interpolation for rate/pitch adjustment
+ ([#2774](https://github.com/google/ExoPlayer/issues/2774)).
+* HLS: Include EXT-X-DATERANGE tags in HlsMediaPlaylist.
+* HLS: Don't expose CEA-608 track if CLOSED-CAPTIONS=NONE
+ ([#2743](https://github.com/google/ExoPlayer/issues/2743)).
+* HLS: Correctly propagate errors loading the media playlist
+ ([#2623](https://github.com/google/ExoPlayer/issues/2623)).
+* UI: DefaultTimeBar enhancements and bug fixes
+ ([#2740](https://github.com/google/ExoPlayer/issues/2740)).
+* Ogg: Fix failure to play some Ogg files
+ ([#2782](https://github.com/google/ExoPlayer/issues/2782)).
+* Captions: Don't select text tack with no language by default.
+* Captions: TTML positioning fixes
+ ([#2824](https://github.com/google/ExoPlayer/issues/2824)).
+* Misc bugfixes.
-### r2.4.0 (2017-04-25) ###
+### r2.4.0 (2017-04-25)
-* New modular library structure. You can read more about depending on individual
- library modules
- [here](https://medium.com/google-exoplayer/exoplayers-new-modular-structure-a916c0874907).
-* Variable speed playback support on API level 16+. You can read more about
- changing the playback speed
- [here](https://medium.com/google-exoplayer/variable-speed-playback-with-exoplayer-e6e6a71e0343)
- ([#26](https://github.com/google/ExoPlayer/issues/26)).
-* New time bar view, including support for displaying ad break markers.
-* Support DVB subtitles in MPEG-TS and MKV.
-* Support adaptive playback for audio only DASH, HLS and SmoothStreaming
- ([#1975](https://github.com/google/ExoPlayer/issues/1975)).
-* Support for setting extractor flags on DefaultExtractorsFactory
- ([#2657](https://github.com/google/ExoPlayer/issues/2657)).
-* Support injecting custom renderers into SimpleExoPlayer using a new
- RenderersFactory interface.
-* Correctly set ExoPlayer's internal thread priority to `THREAD_PRIORITY_AUDIO`.
-* TX3G: Support styling and positioning.
-* FLV:
- * Support MP3 in FLV.
- * Skip unhandled metadata rather than failing
- ([#2634](https://github.com/google/ExoPlayer/issues/2634)).
- * Fix potential OutOfMemory errors.
-* ID3: Better handle malformed ID3 data
- ([#2604](https://github.com/google/ExoPlayer/issues/2604),
- [#2663](https://github.com/google/ExoPlayer/issues/2663)).
-* FFmpeg extension: Fixed build instructions
- ([#2561](https://github.com/google/ExoPlayer/issues/2561)).
-* VP9 extension: Reduced binary size.
-* FLAC extension: Enabled 64 bit targets.
-* Misc bugfixes.
+* New modular library structure. You can read more about depending on
+ individual library modules
+ [here](https://medium.com/google-exoplayer/exoplayers-new-modular-structure-a916c0874907).
+* Variable speed playback support on API level 16+. You can read more about
+ changing the playback speed
+ [here](https://medium.com/google-exoplayer/variable-speed-playback-with-exoplayer-e6e6a71e0343)
+ ([#26](https://github.com/google/ExoPlayer/issues/26)).
+* New time bar view, including support for displaying ad break markers.
+* Support DVB subtitles in MPEG-TS and MKV.
+* Support adaptive playback for audio only DASH, HLS and SmoothStreaming
+ ([#1975](https://github.com/google/ExoPlayer/issues/1975)).
+* Support for setting extractor flags on DefaultExtractorsFactory
+ ([#2657](https://github.com/google/ExoPlayer/issues/2657)).
+* Support injecting custom renderers into SimpleExoPlayer using a new
+ RenderersFactory interface.
+* Correctly set ExoPlayer's internal thread priority to
+ `THREAD_PRIORITY_AUDIO`.
+* TX3G: Support styling and positioning.
+* FLV:
+ * Support MP3 in FLV.
+ * Skip unhandled metadata rather than failing
+ ([#2634](https://github.com/google/ExoPlayer/issues/2634)).
+ * Fix potential OutOfMemory errors.
+* ID3: Better handle malformed ID3 data
+ ([#2604](https://github.com/google/ExoPlayer/issues/2604),
+ [#2663](https://github.com/google/ExoPlayer/issues/2663)).
+* FFmpeg extension: Fixed build instructions
+ ([#2561](https://github.com/google/ExoPlayer/issues/2561)).
+* VP9 extension: Reduced binary size.
+* FLAC extension: Enabled 64 bit targets.
+* Misc bugfixes.
-### r2.3.1 (2017-03-23) ###
+### r2.3.1 (2017-03-23)
-* Fix NPE enabling WebVTT subtitles in DASH streams
- ([#2596](https://github.com/google/ExoPlayer/issues/2596)).
-* Fix skipping to keyframes when MediaCodecVideoRenderer is enabled but without
- a Surface ([#2575](https://github.com/google/ExoPlayer/issues/2575)).
-* Minor fix for CEA-708 decoder
- ([#2595](https://github.com/google/ExoPlayer/issues/2595)).
+* Fix NPE enabling WebVTT subtitles in DASH streams
+ ([#2596](https://github.com/google/ExoPlayer/issues/2596)).
+* Fix skipping to keyframes when MediaCodecVideoRenderer is enabled but
+ without a Surface
+ ([#2575](https://github.com/google/ExoPlayer/issues/2575)).
+* Minor fix for CEA-708 decoder
+ ([#2595](https://github.com/google/ExoPlayer/issues/2595)).
-### r2.3.0 (2017-03-16) ###
+### r2.3.0 (2017-03-16)
-* GVR extension: Wraps the Google VR Audio SDK to provide spatial audio
- rendering. You can read more about the GVR extension
- [here](https://medium.com/google-exoplayer/spatial-audio-with-exoplayer-and-gvr-cecb00e9da5f#.xdjebjd7g).
-* DASH improvements:
- * Support embedded CEA-608 closed captions
- ([#2362](https://github.com/google/ExoPlayer/issues/2362)).
- * Support embedded EMSG events
- ([#2176](https://github.com/google/ExoPlayer/issues/2176)).
- * Support mspr:pro manifest element
- ([#2386](https://github.com/google/ExoPlayer/issues/2386)).
- * Correct handling of empty segment indices at the start of live events
- ([#1865](https://github.com/google/ExoPlayer/issues/1865)).
-* HLS improvements:
- * Respect initial track selection
- ([#2353](https://github.com/google/ExoPlayer/issues/2353)).
- * Reduced frequency of media playlist requests when playback position is close
- to the live edge ([#2548](https://github.com/google/ExoPlayer/issues/2548)).
- * Exposed the master playlist through ExoPlayer.getCurrentManifest()
- ([#2537](https://github.com/google/ExoPlayer/issues/2537)).
- * Support CLOSED-CAPTIONS #EXT-X-MEDIA type
- ([#341](https://github.com/google/ExoPlayer/issues/341)).
- * Fixed handling of negative values in #EXT-X-SUPPORT
- ([#2495](https://github.com/google/ExoPlayer/issues/2495)).
- * Fixed potential endless buffering state for streams with WebVTT subtitles
- ([#2424](https://github.com/google/ExoPlayer/issues/2424)).
-* MPEG-TS improvements:
- * Support for multiple programs.
- * Support for multiple closed captions and caption service descriptors
- ([#2161](https://github.com/google/ExoPlayer/issues/2161)).
-* MP3: Add `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` extractor option to enable
- constant bitrate seeking in MP3 files that would otherwise be unseekable
- ([#2445](https://github.com/google/ExoPlayer/issues/2445)).
-* ID3: Better handle malformed ID3 data
- ([#2486](https://github.com/google/ExoPlayer/issues/2486)).
-* Track selection: Added maxVideoBitrate parameter to DefaultTrackSelector.
-* DRM: Add support for CENC ClearKey on API level 21+
- ([#2361](https://github.com/google/ExoPlayer/issues/2361)).
-* DRM: Support dynamic setting of key request headers
- ([#1924](https://github.com/google/ExoPlayer/issues/1924)).
-* SmoothStreaming: Fixed handling of start_time placeholder
- ([#2447](https://github.com/google/ExoPlayer/issues/2447)).
-* FLAC extension: Fix proguard configuration
- ([#2427](https://github.com/google/ExoPlayer/issues/2427)).
-* Misc bugfixes.
+* GVR extension: Wraps the Google VR Audio SDK to provide spatial audio
+ rendering. You can read more about the GVR extension
+ [here](https://medium.com/google-exoplayer/spatial-audio-with-exoplayer-and-gvr-cecb00e9da5f#.xdjebjd7g).
+* DASH improvements:
+ * Support embedded CEA-608 closed captions
+ ([#2362](https://github.com/google/ExoPlayer/issues/2362)).
+ * Support embedded EMSG events
+ ([#2176](https://github.com/google/ExoPlayer/issues/2176)).
+ * Support mspr:pro manifest element
+ ([#2386](https://github.com/google/ExoPlayer/issues/2386)).
+ * Correct handling of empty segment indices at the start of live events
+ ([#1865](https://github.com/google/ExoPlayer/issues/1865)).
+* HLS improvements:
+ * Respect initial track selection
+ ([#2353](https://github.com/google/ExoPlayer/issues/2353)).
+ * Reduced frequency of media playlist requests when playback position is
+ close to the live edge
+ ([#2548](https://github.com/google/ExoPlayer/issues/2548)).
+ * Exposed the master playlist through ExoPlayer.getCurrentManifest()
+ ([#2537](https://github.com/google/ExoPlayer/issues/2537)).
+ * Support CLOSED-CAPTIONS #EXT-X-MEDIA type
+ ([#341](https://github.com/google/ExoPlayer/issues/341)).
+ * Fixed handling of negative values in #EXT-X-SUPPORT
+ ([#2495](https://github.com/google/ExoPlayer/issues/2495)).
+ * Fixed potential endless buffering state for streams with WebVTT
+ subtitles ([#2424](https://github.com/google/ExoPlayer/issues/2424)).
+* MPEG-TS improvements:
+ * Support for multiple programs.
+ * Support for multiple closed captions and caption service descriptors
+ ([#2161](https://github.com/google/ExoPlayer/issues/2161)).
+* MP3: Add `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` extractor option to enable
+ constant bitrate seeking in MP3 files that would otherwise be unseekable
+ ([#2445](https://github.com/google/ExoPlayer/issues/2445)).
+* ID3: Better handle malformed ID3 data
+ ([#2486](https://github.com/google/ExoPlayer/issues/2486)).
+* Track selection: Added maxVideoBitrate parameter to DefaultTrackSelector.
+* DRM: Add support for CENC ClearKey on API level 21+
+ ([#2361](https://github.com/google/ExoPlayer/issues/2361)).
+* DRM: Support dynamic setting of key request headers
+ ([#1924](https://github.com/google/ExoPlayer/issues/1924)).
+* SmoothStreaming: Fixed handling of start_time placeholder
+ ([#2447](https://github.com/google/ExoPlayer/issues/2447)).
+* FLAC extension: Fix proguard configuration
+ ([#2427](https://github.com/google/ExoPlayer/issues/2427)).
+* Misc bugfixes.
-### r2.2.0 (2017-01-30) ###
+### r2.2.0 (2017-01-30)
-* Demo app: Automatic recovery from BehindLiveWindowException, plus improved
- handling of pausing and resuming live streams
- ([#2344](https://github.com/google/ExoPlayer/issues/2344)).
-* AndroidTV: Added Support for tunneled video playback
- ([#1688](https://github.com/google/ExoPlayer/issues/1688)).
-* DRM: Renamed StreamingDrmSessionManager to DefaultDrmSessionManager and
- added support for using offline licenses
- ([#876](https://github.com/google/ExoPlayer/issues/876)).
-* DRM: Introduce OfflineLicenseHelper to help with offline license acquisition,
- renewal and release.
-* UI: Updated player control assets. Added vector drawables for use on API level
- 21 and above.
-* UI: Made player control seek bar work correctly with key events if focusable
- ([#2278](https://github.com/google/ExoPlayer/issues/2278)).
-* HLS: Improved support for streams that use EXT-X-DISCONTINUITY without
- EXT-X-DISCONTINUITY-SEQUENCE
- ([#1789](https://github.com/google/ExoPlayer/issues/1789)).
-* HLS: Support for EXT-X-START tag
- ([#1544](https://github.com/google/ExoPlayer/issues/1544)).
-* HLS: Check #EXTM3U header is present when parsing the playlist. Fail
- gracefully if not ([#2301](https://github.com/google/ExoPlayer/issues/2301)).
-* HLS: Fix memory leak
- ([#2319](https://github.com/google/ExoPlayer/issues/2319)).
-* HLS: Fix non-seamless first adaptation where master playlist omits resolution
- tags ([#2096](https://github.com/google/ExoPlayer/issues/2096)).
-* HLS: Fix handling of WebVTT subtitle renditions with non-standard segment file
- extensions ([#2025](https://github.com/google/ExoPlayer/issues/2025) and
- [#2355](https://github.com/google/ExoPlayer/issues/2355)).
-* HLS: Better handle inconsistent HLS playlist update
- ([#2249](https://github.com/google/ExoPlayer/issues/2249)).
-* DASH: Don't overflow when dealing with large segment numbers
- ([#2311](https://github.com/google/ExoPlayer/issues/2311)).
-* DASH: Fix propagation of language from the manifest
- ([#2335](https://github.com/google/ExoPlayer/issues/2335)).
-* SmoothStreaming: Work around "Offset to sample data was negative" failures
- ([#2292](https://github.com/google/ExoPlayer/issues/2292),
- [#2101](https://github.com/google/ExoPlayer/issues/2101) and
- [#1152](https://github.com/google/ExoPlayer/issues/1152)).
-* MP3/ID3: Added support for parsing Chapter and URL link frames
- ([#2316](https://github.com/google/ExoPlayer/issues/2316)).
-* MP3/ID3: Handle ID3 frames that end with empty text field
- ([#2309](https://github.com/google/ExoPlayer/issues/2309)).
-* Added ClippingMediaSource for playing clipped portions of media
- ([#1988](https://github.com/google/ExoPlayer/issues/1988)).
-* Added convenience methods to query whether the current window is dynamic and
- seekable ([#2320](https://github.com/google/ExoPlayer/issues/2320)).
-* Support setting of default headers on HttpDataSource.Factory implementations
- ([#2166](https://github.com/google/ExoPlayer/issues/2166)).
-* Fixed cache failures when using an encrypted cache content index.
-* Fix visual artifacts when switching output surface
- ([#2093](https://github.com/google/ExoPlayer/issues/2093)).
-* Fix gradle + proguard configurations.
-* Fix player position when replacing the MediaSource
- ([#2369](https://github.com/google/ExoPlayer/issues/2369)).
-* Misc bug fixes, including
- [#2330](https://github.com/google/ExoPlayer/issues/2330),
- [#2269](https://github.com/google/ExoPlayer/issues/2269),
- [#2252](https://github.com/google/ExoPlayer/issues/2252),
- [#2264](https://github.com/google/ExoPlayer/issues/2264) and
- [#2290](https://github.com/google/ExoPlayer/issues/2290).
+* Demo app: Automatic recovery from BehindLiveWindowException, plus improved
+ handling of pausing and resuming live streams
+ ([#2344](https://github.com/google/ExoPlayer/issues/2344)).
+* AndroidTV: Added Support for tunneled video playback
+ ([#1688](https://github.com/google/ExoPlayer/issues/1688)).
+* DRM: Renamed StreamingDrmSessionManager to DefaultDrmSessionManager and
+ added support for using offline licenses
+ ([#876](https://github.com/google/ExoPlayer/issues/876)).
+* DRM: Introduce OfflineLicenseHelper to help with offline license
+ acquisition, renewal and release.
+* UI: Updated player control assets. Added vector drawables for use on API
+ level 21 and above.
+* UI: Made player control seek bar work correctly with key events if focusable
+ ([#2278](https://github.com/google/ExoPlayer/issues/2278)).
+* HLS: Improved support for streams that use EXT-X-DISCONTINUITY without
+ EXT-X-DISCONTINUITY-SEQUENCE
+ ([#1789](https://github.com/google/ExoPlayer/issues/1789)).
+* HLS: Support for EXT-X-START tag
+ ([#1544](https://github.com/google/ExoPlayer/issues/1544)).
+* HLS: Check #EXTM3U header is present when parsing the playlist. Fail
+ gracefully if not
+ ([#2301](https://github.com/google/ExoPlayer/issues/2301)).
+* HLS: Fix memory leak
+ ([#2319](https://github.com/google/ExoPlayer/issues/2319)).
+* HLS: Fix non-seamless first adaptation where master playlist omits
+ resolution tags ([#2096](https://github.com/google/ExoPlayer/issues/2096)).
+* HLS: Fix handling of WebVTT subtitle renditions with non-standard segment
+ file extensions ([#2025](https://github.com/google/ExoPlayer/issues/2025)
+ and [#2355](https://github.com/google/ExoPlayer/issues/2355)).
+* HLS: Better handle inconsistent HLS playlist update
+ ([#2249](https://github.com/google/ExoPlayer/issues/2249)).
+* DASH: Don't overflow when dealing with large segment numbers
+ ([#2311](https://github.com/google/ExoPlayer/issues/2311)).
+* DASH: Fix propagation of language from the manifest
+ ([#2335](https://github.com/google/ExoPlayer/issues/2335)).
+* SmoothStreaming: Work around "Offset to sample data was negative" failures
+ ([#2292](https://github.com/google/ExoPlayer/issues/2292),
+ [#2101](https://github.com/google/ExoPlayer/issues/2101) and
+ [#1152](https://github.com/google/ExoPlayer/issues/1152)).
+* MP3/ID3: Added support for parsing Chapter and URL link frames
+ ([#2316](https://github.com/google/ExoPlayer/issues/2316)).
+* MP3/ID3: Handle ID3 frames that end with empty text field
+ ([#2309](https://github.com/google/ExoPlayer/issues/2309)).
+* Added ClippingMediaSource for playing clipped portions of media
+ ([#1988](https://github.com/google/ExoPlayer/issues/1988)).
+* Added convenience methods to query whether the current window is dynamic and
+ seekable ([#2320](https://github.com/google/ExoPlayer/issues/2320)).
+* Support setting of default headers on HttpDataSource.Factory implementations
+ ([#2166](https://github.com/google/ExoPlayer/issues/2166)).
+* Fixed cache failures when using an encrypted cache content index.
+* Fix visual artifacts when switching output surface
+ ([#2093](https://github.com/google/ExoPlayer/issues/2093)).
+* Fix gradle + proguard configurations.
+* Fix player position when replacing the MediaSource
+ ([#2369](https://github.com/google/ExoPlayer/issues/2369)).
+* Misc bug fixes, including
+ [#2330](https://github.com/google/ExoPlayer/issues/2330),
+ [#2269](https://github.com/google/ExoPlayer/issues/2269),
+ [#2252](https://github.com/google/ExoPlayer/issues/2252),
+ [#2264](https://github.com/google/ExoPlayer/issues/2264) and
+ [#2290](https://github.com/google/ExoPlayer/issues/2290).
-### r2.1.1 (2016-12-20) ###
+### r2.1.1 (2016-12-20)
-* Fix some subtitle types (e.g. WebVTT) being displayed out of sync
- ([#2208](https://github.com/google/ExoPlayer/issues/2208)).
-* Fix incorrect position reporting for on-demand HLS media that includes
- EXT-X-PROGRAM-DATE-TIME tags
- ([#2224](https://github.com/google/ExoPlayer/issues/2224)).
-* Fix issue where playbacks could get stuck in the initial buffering state if
- over 1MB of data needs to be read to initialize the playback.
+* Fix some subtitle types (e.g. WebVTT) being displayed out of sync
+ ([#2208](https://github.com/google/ExoPlayer/issues/2208)).
+* Fix incorrect position reporting for on-demand HLS media that includes
+ EXT-X-PROGRAM-DATE-TIME tags
+ ([#2224](https://github.com/google/ExoPlayer/issues/2224)).
+* Fix issue where playbacks could get stuck in the initial buffering state if
+ over 1MB of data needs to be read to initialize the playback.
-### r2.1.0 (2016-12-14) ###
+### r2.1.0 (2016-12-14)
-* HLS: Support for seeking in live streams
- ([#87](https://github.com/google/ExoPlayer/issues/87)).
-* HLS: Improved support:
- * Support for EXT-X-PROGRAM-DATE-TIME
- ([#747](https://github.com/google/ExoPlayer/issues/747)).
- * Improved handling of sample timestamps and their alignment across variants
- and renditions.
- * Fix issue that could cause playbacks to get stuck in an endless initial
- buffering state.
- * Correctly propagate BehindLiveWindowException instead of
- IndexOutOfBoundsException exception
- ([#1695](https://github.com/google/ExoPlayer/issues/1695)).
-* MP3/MP4: Support for ID3 metadata, including embedded album art
- ([#979](https://github.com/google/ExoPlayer/issues/979)).
-* Improved customization of UI components. You can read about customization of
- ExoPlayer's UI components
- [here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi).
-* Robustness improvements when handling MediaSource timeline changes and
- MediaPeriod transitions.
-* CEA-608: Support for caption styling and positioning.
-* MPEG-TS: Improved support:
- * Support injection of custom TS payload readers.
- * Support injection of custom section payload readers.
- * Support SCTE-35 splice information messages.
- * Support multiple table sections in a single PSI section.
- * Fix NullPointerException when an unsupported stream type is encountered
- ([#2149](https://github.com/google/ExoPlayer/issues/2149)).
- * Avoid failure when expected ID3 header not found
- ([#1966](https://github.com/google/ExoPlayer/issues/1966)).
-* Improvements to the upstream cache package.
- * Support caching of media segments for DASH, HLS and SmoothStreaming. Note
- that caching of manifest and playlist files is still not supported in the
- (normal) case where the corresponding responses are compressed.
- * Support caching for ExtractorMediaSource based playbacks.
-* Improved flexibility of SimpleExoPlayer
- ([#2102](https://github.com/google/ExoPlayer/issues/2102)).
-* Fix issue where only the audio of a video would play due to capability
- detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007),
- [#2034](https://github.com/google/ExoPlayer/issues/2034) and
- [#2157](https://github.com/google/ExoPlayer/issues/2157)).
-* Fix issues that could cause ExtractorMediaSource based playbacks to get stuck
- buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)).
-* Correctly set SimpleExoPlayerView surface aspect ratio when an active player
- is attached ([#2077](https://github.com/google/ExoPlayer/issues/2077)).
-* OGG: Fix playback of short OGG files
- ([#1976](https://github.com/google/ExoPlayer/issues/1976)).
-* MP4: Support `.mp3` tracks
- ([#2066](https://github.com/google/ExoPlayer/issues/2066)).
-* SubRip: Don't fail playbacks if SubRip file contains negative timestamps
- ([#2145](https://github.com/google/ExoPlayer/issues/2145)).
-* Misc bugfixes.
+* HLS: Support for seeking in live streams
+ ([#87](https://github.com/google/ExoPlayer/issues/87)).
+* HLS: Improved support:
+ * Support for EXT-X-PROGRAM-DATE-TIME
+ ([#747](https://github.com/google/ExoPlayer/issues/747)).
+ * Improved handling of sample timestamps and their alignment across
+ variants and renditions.
+ * Fix issue that could cause playbacks to get stuck in an endless initial
+ buffering state.
+ * Correctly propagate BehindLiveWindowException instead of
+ IndexOutOfBoundsException exception
+ ([#1695](https://github.com/google/ExoPlayer/issues/1695)).
+* MP3/MP4: Support for ID3 metadata, including embedded album art
+ ([#979](https://github.com/google/ExoPlayer/issues/979)).
+* Improved customization of UI components. You can read about customization of
+ ExoPlayer's UI components
+ [here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi).
+* Robustness improvements when handling MediaSource timeline changes and
+ MediaPeriod transitions.
+* CEA-608: Support for caption styling and positioning.
+* MPEG-TS: Improved support:
+ * Support injection of custom TS payload readers.
+ * Support injection of custom section payload readers.
+ * Support SCTE-35 splice information messages.
+ * Support multiple table sections in a single PSI section.
+ * Fix NullPointerException when an unsupported stream type is encountered
+ ([#2149](https://github.com/google/ExoPlayer/issues/2149)).
+ * Avoid failure when expected ID3 header not found
+ ([#1966](https://github.com/google/ExoPlayer/issues/1966)).
+* Improvements to the upstream cache package.
+ * Support caching of media segments for DASH, HLS and SmoothStreaming.
+ Note that caching of manifest and playlist files is still not supported
+ in the (normal) case where the corresponding responses are compressed.
+ * Support caching for ExtractorMediaSource based playbacks.
+* Improved flexibility of SimpleExoPlayer
+ ([#2102](https://github.com/google/ExoPlayer/issues/2102)).
+* Fix issue where only the audio of a video would play due to capability
+ detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007),
+ [#2034](https://github.com/google/ExoPlayer/issues/2034) and
+ [#2157](https://github.com/google/ExoPlayer/issues/2157)).
+* Fix issues that could cause ExtractorMediaSource based playbacks to get
+ stuck buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)).
+* Correctly set SimpleExoPlayerView surface aspect ratio when an active player
+ is attached ([#2077](https://github.com/google/ExoPlayer/issues/2077)).
+* OGG: Fix playback of short OGG files
+ ([#1976](https://github.com/google/ExoPlayer/issues/1976)).
+* MP4: Support `.mp3` tracks
+ ([#2066](https://github.com/google/ExoPlayer/issues/2066)).
+* SubRip: Don't fail playbacks if SubRip file contains negative timestamps
+ ([#2145](https://github.com/google/ExoPlayer/issues/2145)).
+* Misc bugfixes.
-### r2.0.4 (2016-10-20) ###
+### r2.0.4 (2016-10-20)
-* Fix crash on Jellybean devices when using playback controls
- ([#1965](https://github.com/google/ExoPlayer/issues/1965)).
+* Fix crash on Jellybean devices when using playback controls
+ ([#1965](https://github.com/google/ExoPlayer/issues/1965)).
-### r2.0.3 (2016-10-17) ###
+### r2.0.3 (2016-10-17)
-* Fixed NullPointerException in ExtractorMediaSource
- ([#1914](https://github.com/google/ExoPlayer/issues/1914)).
-* Fixed NullPointerException in HlsMediaPeriod
- ([#1907](https://github.com/google/ExoPlayer/issues/1907)).
-* Fixed memory leak in PlaybackControlView
- ([#1908](https://github.com/google/ExoPlayer/issues/1908)).
-* Fixed strict mode violation when using
- SimpleExoPlayer.setVideoPlayerTextureView().
-* Fixed L3 Widevine provisioning
- ([#1925](https://github.com/google/ExoPlayer/issues/1925)).
-* Fixed hiding of controls with use_controller="false"
- ([#1919](https://github.com/google/ExoPlayer/issues/1919)).
-* Improvements to Cronet network stack extension.
-* Misc bug fixes.
+* Fixed NullPointerException in ExtractorMediaSource
+ ([#1914](https://github.com/google/ExoPlayer/issues/1914)).
+* Fixed NullPointerException in HlsMediaPeriod
+ ([#1907](https://github.com/google/ExoPlayer/issues/1907)).
+* Fixed memory leak in PlaybackControlView
+ ([#1908](https://github.com/google/ExoPlayer/issues/1908)).
+* Fixed strict mode violation when using
+ SimpleExoPlayer.setVideoPlayerTextureView().
+* Fixed L3 Widevine provisioning
+ ([#1925](https://github.com/google/ExoPlayer/issues/1925)).
+* Fixed hiding of controls with use_controller="false"
+ ([#1919](https://github.com/google/ExoPlayer/issues/1919)).
+* Improvements to Cronet network stack extension.
+* Misc bug fixes.
-### r2.0.2 (2016-10-06) ###
+### r2.0.2 (2016-10-06)
-* Fixes for MergingMediaSource and sideloaded subtitles.
- ([#1882](https://github.com/google/ExoPlayer/issues/1882),
- [#1854](https://github.com/google/ExoPlayer/issues/1854),
- [#1900](https://github.com/google/ExoPlayer/issues/1900)).
-* Reduced effect of application code leaking player references
- ([#1855](https://github.com/google/ExoPlayer/issues/1855)).
-* Initial support for fragmented MP4 in HLS.
-* Misc bug fixes and minor features.
+* Fixes for MergingMediaSource and sideloaded subtitles.
+ ([#1882](https://github.com/google/ExoPlayer/issues/1882),
+ [#1854](https://github.com/google/ExoPlayer/issues/1854),
+ [#1900](https://github.com/google/ExoPlayer/issues/1900)).
+* Reduced effect of application code leaking player references
+ ([#1855](https://github.com/google/ExoPlayer/issues/1855)).
+* Initial support for fragmented MP4 in HLS.
+* Misc bug fixes and minor features.
-### r2.0.1 (2016-09-30) ###
+### r2.0.1 (2016-09-30)
-* Fix playback of short duration content
- ([#1837](https://github.com/google/ExoPlayer/issues/1837)).
-* Fix MergingMediaSource preparation issue
- ([#1853](https://github.com/google/ExoPlayer/issues/1853)).
-* Fix live stream buffering (out of memory) issue
- ([#1825](https://github.com/google/ExoPlayer/issues/1825)).
+* Fix playback of short duration content
+ ([#1837](https://github.com/google/ExoPlayer/issues/1837)).
+* Fix MergingMediaSource preparation issue
+ ([#1853](https://github.com/google/ExoPlayer/issues/1853)).
+* Fix live stream buffering (out of memory) issue
+ ([#1825](https://github.com/google/ExoPlayer/issues/1825)).
-### r2.0.0 (2016-09-14) ###
+### r2.0.0 (2016-09-14)
ExoPlayer 2.x is a major iteration of the library. It includes significant API
and architectural changes, new features and many bug fixes. You can read about
some of the motivations behind ExoPlayer 2.x
[here](https://medium.com/google-exoplayer/exoplayer-2-x-why-what-and-when-74fd9cb139#.am7h8nytm).
-* Root package name changed to `com.google.android.exoplayer2`. The library
- structure and class names have also been sanitized. Read more
- [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-package-and-class-names-ef8e1d9ba96f#.lv8sd4nez).
-* Key architectural changes:
- * Late binding between rendering and media source components. Allows the same
- rendering components to be re-used from one playback to another. Enables
- features such as gapless playback through playlists and DASH multi-period
- support.
- * Improved track selection design. More details can be found
- [here](https://medium.com/google-exoplayer/exoplayer-2-x-track-selection-2b62ff712cc9#.n00zo76b6).
- * LoadControl now used to control buffering and loading across all playback
- types.
- * Media source components given additional structure. A new MediaSource class
- has been introduced. MediaSources expose Timelines that describe the media
- they expose, and can consist of multiple MediaPeriods. This enables features
- such as seeking in live playbacks and DASH multi-period support.
- * Responsibility for loading the initial DASH/SmoothStreaming/HLS manifest is
- promoted to the corresponding MediaSource components and is no longer the
- application's responsibility.
- * Higher level abstractions such as SimpleExoPlayer have been added to the
- library. These make the library easier to use for common use cases. The demo
- app is halved in size as a result, whilst at the same time gaining more
- functionality. Read more
- [here](https://medium.com/google-exoplayer/exoplayer-2-x-improved-demo-app-d97171aaaaa1).
- * Enhanced library support for implementing audio extensions. Read more
- [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-audio-features-cfb26c2883a#.ua75vu4s3).
- * Format and MediaFormat are replaced by a single Format class.
-* Key new features:
- * Playlist support. Includes support for gapless playback between playlist
- items and consistent application of LoadControl and TrackSelector policies
- when transitioning between items
- ([#1270](https://github.com/google/ExoPlayer/issues/1270)).
- * Seeking in live playbacks for DASH and SmoothStreaming
- ([#291](https://github.com/google/ExoPlayer/issues/291)).
- * DASH multi-period support
- ([#557](https://github.com/google/ExoPlayer/issues/557)).
- * MediaSource composition allows MediaSources to be concatenated into a
- playlist, merged and looped. Read more
- [here](https://medium.com/google-exoplayer/exoplayer-2-x-mediasource-composition-6c285fcbca1f#.zfha8qupz).
- * Looping support (see above)
- ([#490](https://github.com/google/ExoPlayer/issues/490)).
- * Ability to query information about all tracks in a piece of media (including
- those not supported by the device)
- ([#1121](https://github.com/google/ExoPlayer/issues/1121)).
- * Improved player controls.
- * Support for PSSH in fMP4 moof atoms
- ([#1143](https://github.com/google/ExoPlayer/issues/1143)).
- * Support for Opus in Ogg
- ([#1447](https://github.com/google/ExoPlayer/issues/1447)).
- * CacheDataSource support for standalone media file playbacks (mp3, mp4 etc).
- * FFMPEG extension (for audio only).
-* Key bug fixes:
- * Removed unnecessary secondary requests when playing standalone media files
- ([#1041](https://github.com/google/ExoPlayer/issues/1041)).
- * Fixed playback of video only (i.e. no audio) live streams
- ([#758](https://github.com/google/ExoPlayer/issues/758)).
- * Fixed silent failure when media buffer is too small
- ([#583](https://github.com/google/ExoPlayer/issues/583)).
- * Suppressed "Sending message to a Handler on a dead thread" warnings
- ([#426](https://github.com/google/ExoPlayer/issues/426)).
+* Root package name changed to `com.google.android.exoplayer2`. The library
+ structure and class names have also been sanitized. Read more
+ [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-package-and-class-names-ef8e1d9ba96f#.lv8sd4nez).
+* Key architectural changes:
+ * Late binding between rendering and media source components. Allows the
+ same rendering components to be re-used from one playback to another.
+ Enables features such as gapless playback through playlists and DASH
+ multi-period support.
+ * Improved track selection design. More details can be found
+ [here](https://medium.com/google-exoplayer/exoplayer-2-x-track-selection-2b62ff712cc9#.n00zo76b6).
+ * LoadControl now used to control buffering and loading across all
+ playback types.
+ * Media source components given additional structure. A new MediaSource
+ class has been introduced. MediaSources expose Timelines that describe
+ the media they expose, and can consist of multiple MediaPeriods. This
+ enables features such as seeking in live playbacks and DASH multi-period
+ support.
+ * Responsibility for loading the initial DASH/SmoothStreaming/HLS manifest
+ is promoted to the corresponding MediaSource components and is no longer
+ the application's responsibility.
+ * Higher level abstractions such as SimpleExoPlayer have been added to the
+ library. These make the library easier to use for common use cases. The
+ demo app is halved in size as a result, whilst at the same time gaining
+ more functionality. Read more
+ [here](https://medium.com/google-exoplayer/exoplayer-2-x-improved-demo-app-d97171aaaaa1).
+ * Enhanced library support for implementing audio extensions. Read more
+ [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-audio-features-cfb26c2883a#.ua75vu4s3).
+ * Format and MediaFormat are replaced by a single Format class.
+* Key new features:
+ * Playlist support. Includes support for gapless playback between playlist
+ items and consistent application of LoadControl and TrackSelector
+ policies when transitioning between items
+ ([#1270](https://github.com/google/ExoPlayer/issues/1270)).
+ * Seeking in live playbacks for DASH and SmoothStreaming
+ ([#291](https://github.com/google/ExoPlayer/issues/291)).
+ * DASH multi-period support
+ ([#557](https://github.com/google/ExoPlayer/issues/557)).
+ * MediaSource composition allows MediaSources to be concatenated into a
+ playlist, merged and looped. Read more
+ [here](https://medium.com/google-exoplayer/exoplayer-2-x-mediasource-composition-6c285fcbca1f#.zfha8qupz).
+ * Looping support (see above)
+ ([#490](https://github.com/google/ExoPlayer/issues/490)).
+ * Ability to query information about all tracks in a piece of media
+ (including those not supported by the device)
+ ([#1121](https://github.com/google/ExoPlayer/issues/1121)).
+ * Improved player controls.
+ * Support for PSSH in fMP4 moof atoms
+ ([#1143](https://github.com/google/ExoPlayer/issues/1143)).
+ * Support for Opus in Ogg
+ ([#1447](https://github.com/google/ExoPlayer/issues/1447)).
+ * CacheDataSource support for standalone media file playbacks (mp3, mp4
+ etc).
+ * FFMPEG extension (for audio only).
+* Key bug fixes:
+ * Removed unnecessary secondary requests when playing standalone media
+ files ([#1041](https://github.com/google/ExoPlayer/issues/1041)).
+ * Fixed playback of video only (i.e. no audio) live streams
+ ([#758](https://github.com/google/ExoPlayer/issues/758)).
+ * Fixed silent failure when media buffer is too small
+ ([#583](https://github.com/google/ExoPlayer/issues/583)).
+ * Suppressed "Sending message to a Handler on a dead thread" warnings
+ ([#426](https://github.com/google/ExoPlayer/issues/426)).
-# Legacy release notes #
+# Legacy release notes
Note: Since ExoPlayer V1 is still being maintained alongside V2, there is some
overlap between these notes and the notes above. r2.0.0 followed from r1.5.11,
@@ -1958,197 +2176,197 @@
however it can be assumed that all such changes are included in the most recent
V2 release.
-### r1.5.16 ###
+### r1.5.16
-* VP9 extension: Reduced binary size.
-* FLAC extension: Enabled 64 bit targets and fixed proguard config.
-* Misc bugfixes.
+* VP9 extension: Reduced binary size.
+* FLAC extension: Enabled 64 bit targets and fixed proguard config.
+* Misc bugfixes.
-### r1.5.15 ###
+### r1.5.15
-* SmoothStreaming: Fixed handling of start_time placeholder
- ([#2447](https://github.com/google/ExoPlayer/issues/2447)).
-* Misc bugfixes.
+* SmoothStreaming: Fixed handling of start_time placeholder
+ ([#2447](https://github.com/google/ExoPlayer/issues/2447)).
+* Misc bugfixes.
-### r1.5.14 ###
+### r1.5.14
-* Fixed cache failures when using an encrypted cache content index.
-* SmoothStreaming: Work around "Offset to sample data was negative" failures
- ([#2292](https://github.com/google/ExoPlayer/issues/2292),
- [#2101](https://github.com/google/ExoPlayer/issues/2101) and
- [#1152](https://github.com/google/ExoPlayer/issues/1152)).
+* Fixed cache failures when using an encrypted cache content index.
+* SmoothStreaming: Work around "Offset to sample data was negative" failures
+ ([#2292](https://github.com/google/ExoPlayer/issues/2292),
+ [#2101](https://github.com/google/ExoPlayer/issues/2101) and
+ [#1152](https://github.com/google/ExoPlayer/issues/1152)).
-### r1.5.13 ###
+### r1.5.13
-* Improvements to the upstream cache package.
-* MP4: Support `.mp3` tracks
- ([#2066](https://github.com/google/ExoPlayer/issues/2066)).
-* SubRip: Don't fail playbacks if SubRip file contains negative timestamps
- ([#2145](https://github.com/google/ExoPlayer/issues/2145)).
-* MPEG-TS: Avoid failure when expected ID3 header not found
- ([#1966](https://github.com/google/ExoPlayer/issues/1966)).
-* Misc bugfixes.
+* Improvements to the upstream cache package.
+* MP4: Support `.mp3` tracks
+ ([#2066](https://github.com/google/ExoPlayer/issues/2066)).
+* SubRip: Don't fail playbacks if SubRip file contains negative timestamps
+ ([#2145](https://github.com/google/ExoPlayer/issues/2145)).
+* MPEG-TS: Avoid failure when expected ID3 header not found
+ ([#1966](https://github.com/google/ExoPlayer/issues/1966)).
+* Misc bugfixes.
-### r1.5.12 ###
+### r1.5.12
-* Improvements to Cronet network stack extension.
-* Fix bug in demo app introduced in r1.5.11 that caused L3 Widevine
- provisioning requests to fail.
-* Misc bugfixes.
+* Improvements to Cronet network stack extension.
+* Fix bug in demo app introduced in r1.5.11 that caused L3 Widevine
+ provisioning requests to fail.
+* Misc bugfixes.
-### r1.5.11 ###
+### r1.5.11
-* Cronet network stack extension.
-* HLS: Fix propagation of language for alternative audio renditions
- ([#1784](https://github.com/google/ExoPlayer/issues/1784)).
-* WebM: Support for subsample encryption.
-* ID3: Fix EOS detection for 2-byte encodings
- ([#1774](https://github.com/google/ExoPlayer/issues/1774)).
-* MPEG-TS: Support multiple tracks of the same type.
-* MPEG-TS: Work toward robust handling of stream corruption.
-* Fix ContentDataSource failures triggered by garbage collector
- ([#1759](https://github.com/google/ExoPlayer/issues/1759)).
+* Cronet network stack extension.
+* HLS: Fix propagation of language for alternative audio renditions
+ ([#1784](https://github.com/google/ExoPlayer/issues/1784)).
+* WebM: Support for subsample encryption.
+* ID3: Fix EOS detection for 2-byte encodings
+ ([#1774](https://github.com/google/ExoPlayer/issues/1774)).
+* MPEG-TS: Support multiple tracks of the same type.
+* MPEG-TS: Work toward robust handling of stream corruption.
+* Fix ContentDataSource failures triggered by garbage collector
+ ([#1759](https://github.com/google/ExoPlayer/issues/1759)).
-### r1.5.10 ###
+### r1.5.10
-* HLS: Stability fixes.
-* MP4: Support for stz2 Atoms.
-* Enable 4K format selection on Sony AndroidTV + nVidia SHIELD.
-* TX3G caption fixes.
+* HLS: Stability fixes.
+* MP4: Support for stz2 Atoms.
+* Enable 4K format selection on Sony AndroidTV + nVidia SHIELD.
+* TX3G caption fixes.
-### r1.5.9 ###
+### r1.5.9
-* MP4: Fixed incorrect sniffing in some cases (#1523).
-* MP4: Improved file compatibility (#1567).
-* ID3: Support for TIT2 and APIC frames.
-* Fixed querying of platform decoders on some devices.
-* Misc bug fixes.
+* MP4: Fixed incorrect sniffing in some cases (#1523).
+* MP4: Improved file compatibility (#1567).
+* ID3: Support for TIT2 and APIC frames.
+* Fixed querying of platform decoders on some devices.
+* Misc bug fixes.
-### r1.5.8 ###
+### r1.5.8
-* HLS: Fix handling of HTTP redirects.
-* Audio: Minor adjustment to improve A/V sync.
-* OGG: Support FLAC in OGG.
-* TTML: Support regions.
-* WAV/PCM: Support 8, 24 and 32-bit WAV and PCM audio.
-* Misc bug fixes and performance optimizations.
+* HLS: Fix handling of HTTP redirects.
+* Audio: Minor adjustment to improve A/V sync.
+* OGG: Support FLAC in OGG.
+* TTML: Support regions.
+* WAV/PCM: Support 8, 24 and 32-bit WAV and PCM audio.
+* Misc bug fixes and performance optimizations.
-### r1.5.7 ###
+### r1.5.7
-* OGG: Support added for OGG.
-* FLAC: Support for FLAC extraction and playback (via an extension).
-* HLS: Multiple audio track support (via Renditions).
-* FMP4: Support multiple tracks in fragmented MP4 (not applicable to
- DASH/SmoothStreaming).
-* WAV: Support for 16-bit WAV files.
-* MKV: Support non-square pixel formats.
-* Misc bug fixes.
+* OGG: Support added for OGG.
+* FLAC: Support for FLAC extraction and playback (via an extension).
+* HLS: Multiple audio track support (via Renditions).
+* FMP4: Support multiple tracks in fragmented MP4 (not applicable to
+ DASH/SmoothStreaming).
+* WAV: Support for 16-bit WAV files.
+* MKV: Support non-square pixel formats.
+* Misc bug fixes.
-### r1.5.6 ###
+### r1.5.6
-* MP3: Fix mono streams playing at 2x speed on some MediaTek based devices
- (#801).
-* MP3: Fix playback of some streams when stream length is unknown.
-* ID3: Support multiple frames of the same type in a single tag.
-* CEA-608: Correctly handle repeated control characters, fixing an issue in
- which captions would immediately disappear.
-* AVC3: Fix decoder failures on some MediaTek devices in the case where the
- first buffer fed to the decoder does not start with SPS/PPS NAL units.
-* Misc bug fixes.
+* MP3: Fix mono streams playing at 2x speed on some MediaTek based devices
+ (#801).
+* MP3: Fix playback of some streams when stream length is unknown.
+* ID3: Support multiple frames of the same type in a single tag.
+* CEA-608: Correctly handle repeated control characters, fixing an issue in
+ which captions would immediately disappear.
+* AVC3: Fix decoder failures on some MediaTek devices in the case where the
+ first buffer fed to the decoder does not start with SPS/PPS NAL units.
+* Misc bug fixes.
-### r1.5.5 ###
+### r1.5.5
-* DASH: Enable MP4 embedded WebVTT playback (#1185)
-* HLS: Fix handling of extended ID3 tags in MPEG-TS (#1181)
-* MP3: Fix incorrect position calculation in VBRI header (#1197)
-* Fix issue seeking backward using SingleSampleSource (#1193)
+* DASH: Enable MP4 embedded WebVTT playback (#1185)
+* HLS: Fix handling of extended ID3 tags in MPEG-TS (#1181)
+* MP3: Fix incorrect position calculation in VBRI header (#1197)
+* Fix issue seeking backward using SingleSampleSource (#1193)
-### r1.5.4 ###
+### r1.5.4
-* HLS: Support for variant selection and WebVtt subtitles.
-* MP4: Support for embedded WebVtt.
-* Improved device compatibility.
-* Fix for resource leak (Issue #1066).
-* Misc bug fixes + minor features.
+* HLS: Support for variant selection and WebVtt subtitles.
+* MP4: Support for embedded WebVtt.
+* Improved device compatibility.
+* Fix for resource leak (Issue #1066).
+* Misc bug fixes + minor features.
-### r1.5.3 ###
+### r1.5.3
-* Support for FLV (without seeking).
-* MP4: Fix for playback of media containing basic edit lists.
-* QuickTime: Fix parsing of QuickTime style audio sample entry.
-* HLS: Add H262 support for devices that have an H262 decoder.
-* Allow AudioTrack PlaybackParams (e.g. speed/pitch) on API level 23+.
-* Correctly detect 4K displays on API level 23+.
-* Misc bug fixes.
+* Support for FLV (without seeking).
+* MP4: Fix for playback of media containing basic edit lists.
+* QuickTime: Fix parsing of QuickTime style audio sample entry.
+* HLS: Add H262 support for devices that have an H262 decoder.
+* Allow AudioTrack PlaybackParams (e.g. speed/pitch) on API level 23+.
+* Correctly detect 4K displays on API level 23+.
+* Misc bug fixes.
-### r1.5.2 ###
+### r1.5.2
-* MPEG-TS/HLS: Fix frame drops playing H265 video.
-* SmoothStreaming: Fix parsing of ProtectionHeader.
+* MPEG-TS/HLS: Fix frame drops playing H265 video.
+* SmoothStreaming: Fix parsing of ProtectionHeader.
-### r1.5.1 ###
+### r1.5.1
-* Enable smooth frame release by default.
-* Added OkHttpDataSource extension.
-* AndroidTV: Correctly detect 4K display size on Bravia devices.
-* FMP4: Handle non-sample data in mdat boxes.
-* TTML: Fix parsing of some colors on Jellybean.
-* SmoothStreaming: Ignore tfdt boxes.
-* Misc bug fixes.
+* Enable smooth frame release by default.
+* Added OkHttpDataSource extension.
+* AndroidTV: Correctly detect 4K display size on Bravia devices.
+* FMP4: Handle non-sample data in mdat boxes.
+* TTML: Fix parsing of some colors on Jellybean.
+* SmoothStreaming: Ignore tfdt boxes.
+* Misc bug fixes.
-### r1.5.0 ###
+### r1.5.0
-* Multi-track support.
-* DASH: Limited support for multi-period manifests.
-* HLS: Smoother format adaptation.
-* HLS: Support for MP3 media segments.
-* TTML: Support for most embedded TTML styling.
-* WebVTT: Enhanced positioning support.
-* Initial playback tests.
-* Misc bug fixes.
+* Multi-track support.
+* DASH: Limited support for multi-period manifests.
+* HLS: Smoother format adaptation.
+* HLS: Support for MP3 media segments.
+* TTML: Support for most embedded TTML styling.
+* WebVTT: Enhanced positioning support.
+* Initial playback tests.
+* Misc bug fixes.
-### r1.4.2 ###
+### r1.4.2
-* Implemented automatic format detection for regular container formats.
-* Added UdpDataSource for connecting to multicast streams.
-* Improved robustness for MP4 playbacks.
-* Misc bug fixes.
+* Implemented automatic format detection for regular container formats.
+* Added UdpDataSource for connecting to multicast streams.
+* Improved robustness for MP4 playbacks.
+* Misc bug fixes.
-### r1.4.1 ###
+### r1.4.1
-* HLS: Fix premature playback failures that could occur in some cases.
+* HLS: Fix premature playback failures that could occur in some cases.
-### r1.4.0 ###
+### r1.4.0
-* Support for extracting Matroska streams (implemented by WebmExtractor).
-* Support for tx3g captions in MP4 streams.
-* Support for H.265 in MPEG-TS streams on supported devices.
-* HLS: Added support for MPEG audio (e.g. MP3) in TS media segments.
-* HLS: Improved robustness against missing chunks and variants.
-* MP4: Added support for embedded MPEG audio (e.g. MP3).
-* TTML: Improved handling of whitespace.
-* DASH: Support Mpd.Location element.
-* Add option to TsExtractor to allow non-IDR keyframes.
-* Added MulticastDataSource for connecting to multicast streams.
-* (WorkInProgress) - First steps to supporting seeking in DASH DVR window.
-* (WorkInProgress) - First steps to supporting styled + positioned subtitles.
-* Misc bug fixes.
+* Support for extracting Matroska streams (implemented by WebmExtractor).
+* Support for tx3g captions in MP4 streams.
+* Support for H.265 in MPEG-TS streams on supported devices.
+* HLS: Added support for MPEG audio (e.g. MP3) in TS media segments.
+* HLS: Improved robustness against missing chunks and variants.
+* MP4: Added support for embedded MPEG audio (e.g. MP3).
+* TTML: Improved handling of whitespace.
+* DASH: Support Mpd.Location element.
+* Add option to TsExtractor to allow non-IDR keyframes.
+* Added MulticastDataSource for connecting to multicast streams.
+* (WorkInProgress) - First steps to supporting seeking in DASH DVR window.
+* (WorkInProgress) - First steps to supporting styled + positioned subtitles.
+* Misc bug fixes.
-### r1.3.3 ###
+### r1.3.3
-* HLS: Fix failure when playing HLS AAC streams.
-* Misc bug fixes.
+* HLS: Fix failure when playing HLS AAC streams.
+* Misc bug fixes.
-### r1.3.2 ###
+### r1.3.2
-* DataSource improvements: `DefaultUriDataSource` now handles http://, https://,
- file://, asset:// and content:// URIs automatically. It also handles
- file:///android_asset/* URIs, and file paths like /path/to/media.mp4 where the
- scheme is omitted.
-* HLS: Fix for some ID3 events being dropped.
-* HLS: Correctly handle 0x0 and floating point RESOLUTION tags.
-* Mp3Extractor: robustness improvements.
+* DataSource improvements: `DefaultUriDataSource` now handles http://,
+ https://, file://, asset:// and content:// URIs automatically. It also
+ handles file:///android_asset/* URIs, and file paths like /path/to/media.mp4
+ where the scheme is omitted.
+* HLS: Fix for some ID3 events being dropped.
+* HLS: Correctly handle 0x0 and floating point RESOLUTION tags.
+* Mp3Extractor: robustness improvements.
-### r1.3.1 ###
+### r1.3.1
-* No notes provided.
+* No notes provided.
diff --git a/tree/constants.gradle b/tree/constants.gradle
index f81a6b4..1a78405 100644
--- a/tree/constants.gradle
+++ b/tree/constants.gradle
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,20 +13,20 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
- releaseVersion = '2.11.2'
- releaseVersionCode = 2011002
+ releaseVersion = '2.11.4'
+ releaseVersionCode = 2011004
minSdkVersion = 16
appTargetSdkVersion = 29
- targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
+ targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
compileSdkVersion = 29
dexmakerVersion = '2.21.0'
junitVersion = '4.13-rc-2'
- guavaVersion = '23.5-android'
+ guavaVersion = '28.2-android'
mockitoVersion = '2.25.0'
robolectricVersion = '4.3.1'
checkerframeworkVersion = '2.5.0'
jsr305Version = '3.0.2'
- kotlinAnnotationsVersion = '1.3.31'
+ kotlinAnnotationsVersion = '1.3.70'
androidxAnnotationVersion = '1.1.0'
androidxAppCompatVersion = '1.1.0'
androidxCollectionVersion = '1.1.0'
diff --git a/tree/core_settings.gradle b/tree/core_settings.gradle
index 2c6ddbd..ac56933 100644
--- a/tree/core_settings.gradle
+++ b/tree/core_settings.gradle
@@ -26,6 +26,7 @@
include modulePrefix + 'library-smoothstreaming'
include modulePrefix + 'library-ui'
include modulePrefix + 'testutils'
+include modulePrefix + 'testdata'
include modulePrefix + 'extension-av1'
include modulePrefix + 'extension-ffmpeg'
include modulePrefix + 'extension-flac'
@@ -51,6 +52,7 @@
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming')
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui')
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils')
+project(modulePrefix + 'testdata').projectDir = new File(rootDir, 'testdata')
project(modulePrefix + 'extension-av1').projectDir = new File(rootDir, 'extensions/av1')
project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg')
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
diff --git a/tree/demos/cast/build.gradle b/tree/demos/cast/build.gradle
index f9228e4..c929f09 100644
--- a/tree/demos/cast/build.gradle
+++ b/tree/demos/cast/build.gradle
@@ -57,8 +57,8 @@
implementation project(modulePrefix + 'library-ui')
implementation project(modulePrefix + 'extension-cast')
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
- implementation 'androidx.recyclerview:recyclerview:1.0.0'
- implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'com.google.android.material:material:1.1.0'
}
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
diff --git a/tree/demos/cast/src/main/AndroidManifest.xml b/tree/demos/cast/src/main/AndroidManifest.xml
index dbfdd83..d92d9e2 100644
--- a/tree/demos/cast/src/main/AndroidManifest.xml
+++ b/tree/demos/cast/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-sdk/>
diff --git a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
index dacdbfe..68b9d05 100644
--- a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
+++ b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
@@ -17,8 +17,8 @@
import android.net.Uri;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ext.cast.MediaItem;
-import com.google.android.exoplayer2.ext.cast.MediaItem.DrmConfiguration;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
@@ -41,60 +41,53 @@
// Clear content.
samples.add(
new MediaItem.Builder()
- .setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
- .setTitle("Clear DASH: Tears")
+ .setSourceUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear DASH: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.build());
samples.add(
new MediaItem.Builder()
- .setUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8")
- .setTitle("Clear HLS: Angel one")
+ .setSourceUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8")
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear HLS: Angel one").build())
.setMimeType(MIME_TYPE_HLS)
.build());
samples.add(
new MediaItem.Builder()
- .setUri("https://html5demos.com/assets/dizzy.mp4")
- .setTitle("Clear MP4: Dizzy")
+ .setSourceUri("https://html5demos.com/assets/dizzy.mp4")
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear MP4: Dizzy").build())
.setMimeType(MIME_TYPE_VIDEO_MP4)
.build());
// DRM content.
samples.add(
new MediaItem.Builder()
- .setUri(Uri.parse("https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd"))
- .setTitle("Widevine DASH cenc: Tears")
+ .setSourceUri(
+ Uri.parse("https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd"))
+ .setMediaMetadata(
+ new MediaMetadata.Builder().setTitle("Widevine DASH cenc: Tears").build())
.setMimeType(MIME_TYPE_DASH)
- .setDrmConfiguration(
- new DrmConfiguration(
- C.WIDEVINE_UUID,
- Uri.parse("https://proxy.uat.widevine.com/proxy?provider=widevine_test"),
- Collections.emptyMap()))
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build());
samples.add(
new MediaItem.Builder()
- .setUri(
- Uri.parse(
- "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd"))
- .setTitle("Widevine DASH cbc1: Tears")
+ .setSourceUri(
+ "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd")
+ .setMediaMetadata(
+ new MediaMetadata.Builder().setTitle("Widevine DASH cbc1: Tears").build())
.setMimeType(MIME_TYPE_DASH)
- .setDrmConfiguration(
- new DrmConfiguration(
- C.WIDEVINE_UUID,
- Uri.parse("https://proxy.uat.widevine.com/proxy?provider=widevine_test"),
- Collections.emptyMap()))
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build());
samples.add(
new MediaItem.Builder()
- .setUri(
- Uri.parse(
- "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd"))
- .setTitle("Widevine DASH cbcs: Tears")
+ .setSourceUri(
+ "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd")
+ .setMediaMetadata(
+ new MediaMetadata.Builder().setTitle("Widevine DASH cbcs: Tears").build())
.setMimeType(MIME_TYPE_DASH)
- .setDrmConfiguration(
- new DrmConfiguration(
- C.WIDEVINE_UUID,
- Uri.parse("https://proxy.uat.widevine.com/proxy?provider=widevine_test"),
- Collections.emptyMap()))
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build());
SAMPLES = Collections.unmodifiableList(samples);
diff --git a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
index 0c5b503..cf8b02a 100644
--- a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
+++ b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
@@ -37,10 +37,12 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.ext.cast.MediaItem;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
@@ -171,8 +173,6 @@
showToast(R.string.error_unsupported_audio);
} else if (trackType == C.TRACK_TYPE_VIDEO) {
showToast(R.string.error_unsupported_video);
- } else {
- // Do nothing.
}
}
@@ -199,6 +199,7 @@
private class MediaQueueListAdapter extends RecyclerView.Adapter<QueueItemViewHolder> {
@Override
+ @NonNull
public QueueItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
@@ -207,9 +208,10 @@
@Override
public void onBindViewHolder(QueueItemViewHolder holder, int position) {
- holder.item = playerManager.getItem(position);
+ holder.item = Assertions.checkNotNull(playerManager.getItem(position));
+
TextView view = holder.textView;
- view.setText(holder.item.title);
+ view.setText(holder.item.mediaMetadata.title);
// TODO: Solve coloring using the theme's ColorStateList.
view.setTextColor(
ColorUtils.setAlphaComponent(
@@ -236,7 +238,9 @@
}
@Override
- public boolean onMove(RecyclerView list, RecyclerView.ViewHolder origin,
+ public boolean onMove(
+ @NonNull RecyclerView list,
+ RecyclerView.ViewHolder origin,
RecyclerView.ViewHolder target) {
int fromPosition = origin.getAdapterPosition();
int toPosition = target.getAdapterPosition();
@@ -261,7 +265,7 @@
}
@Override
- public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
+ public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (draggingFromPosition != C.INDEX_UNSET) {
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
@@ -300,11 +304,11 @@
super(context, android.R.layout.simple_list_item_1, DemoUtil.SAMPLES);
}
- @NonNull
@Override
+ @NonNull
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getView(position, convertView, parent);
- ((TextView) view).setText(getItem(position).title);
+ ((TextView) view).setText(Util.castNonNull(getItem(position)).mediaMetadata.title);
return view;
}
}
diff --git a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
index 85104e0..c5dfe70 100644
--- a/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
+++ b/tree/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
@@ -16,45 +16,29 @@
package com.google.android.exoplayer2.castdemo;
import android.content.Context;
-import android.net.Uri;
import android.view.KeyEvent;
import android.view.View;
+import androidx.annotation.NonNull;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Player.EventListener;
import com.google.android.exoplayer2.Player.TimelineChangeReason;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.Timeline.Period;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
-import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.ext.cast.CastPlayer;
-import com.google.android.exoplayer2.ext.cast.DefaultMediaItemConverter;
-import com.google.android.exoplayer2.ext.cast.MediaItem;
-import com.google.android.exoplayer2.ext.cast.MediaItemConverter;
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Util;
-import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.framework.CastContext;
import java.util.ArrayList;
-import java.util.Map;
/** Manages players and an internal media queue for the demo app. */
/* package */ class PlayerManager implements EventListener, SessionAvailabilityListener {
@@ -77,6 +61,7 @@
private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
new DefaultHttpDataSourceFactory(USER_AGENT);
+ private final DefaultMediaSourceFactory defaultMediaSourceFactory;
private final PlayerView localPlayerView;
private final PlayerControlView castControlView;
private final DefaultTrackSelector trackSelector;
@@ -84,8 +69,6 @@
private final CastPlayer castPlayer;
private final ArrayList<MediaItem> mediaQueue;
private final Listener listener;
- private final ConcatenatingMediaSource concatenatingMediaSource;
- private final MediaItemConverter mediaItemConverter;
private TrackGroupArray lastSeenTrackGroupArray;
private int currentItemIndex;
@@ -111,11 +94,10 @@
this.castControlView = castControlView;
mediaQueue = new ArrayList<>();
currentItemIndex = C.INDEX_UNSET;
- concatenatingMediaSource = new ConcatenatingMediaSource();
- mediaItemConverter = new DefaultMediaItemConverter();
trackSelector = new DefaultTrackSelector(context);
exoPlayer = new SimpleExoPlayer.Builder(context).setTrackSelector(trackSelector).build();
+ defaultMediaSourceFactory = DefaultMediaSourceFactory.newInstance(context, DATA_SOURCE_FACTORY);
exoPlayer.addListener(this);
localPlayerView.setPlayer(exoPlayer);
@@ -135,7 +117,7 @@
* @param itemIndex The index of the item to play.
*/
public void selectQueueItem(int itemIndex) {
- setCurrentItem(itemIndex, C.TIME_UNSET, true);
+ setCurrentItem(itemIndex);
}
/** Returns the index of the currently played item. */
@@ -150,10 +132,7 @@
*/
public void addItem(MediaItem item) {
mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- if (currentPlayer == castPlayer) {
- castPlayer.addItems(mediaItemConverter.toMediaQueueItem(item));
- }
+ currentPlayer.addMediaItem(item);
}
/** Returns the size of the media queue. */
@@ -182,16 +161,7 @@
if (itemIndex == -1) {
return false;
}
- concatenatingMediaSource.removeMediaSource(itemIndex);
- if (currentPlayer == castPlayer) {
- if (castPlayer.getPlaybackState() != Player.STATE_IDLE) {
- Timeline castTimeline = castPlayer.getCurrentTimeline();
- if (castTimeline.getPeriodCount() <= itemIndex) {
- return false;
- }
- castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id);
- }
- }
+ currentPlayer.removeMediaItem(itemIndex);
mediaQueue.remove(itemIndex);
if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
@@ -205,34 +175,25 @@
* Moves an item within the queue.
*
* @param item The item to move.
- * @param toIndex The target index of the item in the queue.
+ * @param newIndex The target index of the item in the queue.
* @return Whether the item move was successful.
*/
- public boolean moveItem(MediaItem item, int toIndex) {
+ public boolean moveItem(MediaItem item, int newIndex) {
int fromIndex = mediaQueue.indexOf(item);
if (fromIndex == -1) {
return false;
}
- // Player update.
- concatenatingMediaSource.moveMediaSource(fromIndex, toIndex);
- if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) {
- Timeline castTimeline = castPlayer.getCurrentTimeline();
- int periodCount = castTimeline.getPeriodCount();
- if (periodCount <= fromIndex || periodCount <= toIndex) {
- return false;
- }
- int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id;
- castPlayer.moveItem(elementId, toIndex);
- }
- mediaQueue.add(toIndex, mediaQueue.remove(fromIndex));
+ // Player update.
+ currentPlayer.moveMediaItem(fromIndex, newIndex);
+ mediaQueue.add(newIndex, mediaQueue.remove(fromIndex));
// Index update.
if (fromIndex == currentItemIndex) {
- maybeSetCurrentItemAndNotify(toIndex);
- } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) {
+ maybeSetCurrentItemAndNotify(newIndex);
+ } else if (fromIndex < currentItemIndex && newIndex >= currentItemIndex) {
maybeSetCurrentItemAndNotify(currentItemIndex - 1);
- } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) {
+ } else if (fromIndex > currentItemIndex && newIndex <= currentItemIndex) {
maybeSetCurrentItemAndNotify(currentItemIndex + 1);
}
@@ -257,7 +218,6 @@
public void release() {
currentItemIndex = C.INDEX_UNSET;
mediaQueue.clear();
- concatenatingMediaSource.clear();
castPlayer.setSessionAvailabilityListener(null);
castPlayer.release();
localPlayerView.setPlayer(null);
@@ -267,7 +227,7 @@
// Player.EventListener implementation.
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
updateCurrentItemIndex();
}
@@ -277,12 +237,13 @@
}
@Override
- public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {
+ public void onTimelineChanged(@NonNull Timeline timeline, @TimelineChangeReason int reason) {
updateCurrentItemIndex();
}
@Override
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(
+ @NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
if (currentPlayer == exoPlayer && trackGroups != lastSeenTrackGroupArray) {
MappingTrackSelector.MappedTrackInfo mappedTrackInfo =
trackSelector.getCurrentMappedTrackInfo();
@@ -360,35 +321,26 @@
this.currentPlayer = currentPlayer;
// Media queue management.
- if (currentPlayer == exoPlayer) {
- exoPlayer.prepare(concatenatingMediaSource);
- }
-
- // Playback transition.
- if (windowIndex != C.INDEX_UNSET) {
- setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
- }
+ currentPlayer.setMediaItems(mediaQueue, windowIndex, playbackPositionMs);
+ currentPlayer.setPlayWhenReady(playWhenReady);
+ currentPlayer.prepare();
}
/**
- * Starts playback of the item at the given position.
+ * Starts playback of the item at the given index.
*
* @param itemIndex The index of the item to play.
- * @param positionMs The position at which playback should start.
- * @param playWhenReady Whether the player should proceed when ready to do so.
*/
- private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
+ private void setCurrentItem(int itemIndex) {
maybeSetCurrentItemAndNotify(itemIndex);
- if (currentPlayer == castPlayer && castPlayer.getCurrentTimeline().isEmpty()) {
- MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()];
- for (int i = 0; i < items.length; i++) {
- items[i] = mediaItemConverter.toMediaQueueItem(mediaQueue.get(i));
- }
- castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF);
+ if (currentPlayer.getCurrentTimeline().getWindowCount() != mediaQueue.size()) {
+ // This only happens with the cast player. The receiver app in the cast device clears the
+ // timeline when the last item of the timeline has been played to end.
+ currentPlayer.setMediaItems(mediaQueue, itemIndex, C.TIME_UNSET);
} else {
- currentPlayer.seekTo(itemIndex, positionMs);
- currentPlayer.setPlayWhenReady(playWhenReady);
+ currentPlayer.seekTo(itemIndex, C.TIME_UNSET);
}
+ currentPlayer.setPlayWhenReady(true);
}
private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
@@ -398,62 +350,4 @@
listener.onQueuePositionChanged(oldIndex, currentItemIndex);
}
}
-
- private MediaSource buildMediaSource(MediaItem item) {
- Uri uri = item.uri;
- String mimeType = item.mimeType;
- if (mimeType == null) {
- throw new IllegalArgumentException("mimeType is required");
- }
-
- DrmSessionManager<ExoMediaCrypto> drmSessionManager =
- DrmSessionManager.getDummyDrmSessionManager();
- MediaItem.DrmConfiguration drmConfiguration = item.drmConfiguration;
- if (drmConfiguration != null && Util.SDK_INT >= 18) {
- String licenseServerUrl =
- drmConfiguration.licenseUri != null ? drmConfiguration.licenseUri.toString() : "";
- HttpMediaDrmCallback drmCallback =
- new HttpMediaDrmCallback(licenseServerUrl, DATA_SOURCE_FACTORY);
- for (Map.Entry<String, String> requestHeader : drmConfiguration.requestHeaders.entrySet()) {
- drmCallback.setKeyRequestProperty(requestHeader.getKey(), requestHeader.getValue());
- }
- drmSessionManager =
- new DefaultDrmSessionManager.Builder()
- .setMultiSession(/* multiSession= */ true)
- .setUuidAndExoMediaDrmProvider(
- drmConfiguration.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
- .build(drmCallback);
- }
-
- MediaSource createdMediaSource;
- switch (mimeType) {
- case DemoUtil.MIME_TYPE_SS:
- createdMediaSource =
- new SsMediaSource.Factory(DATA_SOURCE_FACTORY)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- break;
- case DemoUtil.MIME_TYPE_DASH:
- createdMediaSource =
- new DashMediaSource.Factory(DATA_SOURCE_FACTORY)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- break;
- case DemoUtil.MIME_TYPE_HLS:
- createdMediaSource =
- new HlsMediaSource.Factory(DATA_SOURCE_FACTORY)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- break;
- case DemoUtil.MIME_TYPE_VIDEO_MP4:
- createdMediaSource =
- new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- break;
- default:
- throw new IllegalArgumentException("mimeType is unsupported: " + mimeType);
- }
- return createdMediaSource;
- }
}
diff --git a/tree/demos/gl/README.md b/tree/demos/gl/README.md
new file mode 100644
index 0000000..12dabe9
--- /dev/null
+++ b/tree/demos/gl/README.md
@@ -0,0 +1,11 @@
+# ExoPlayer GL demo
+
+This app demonstrates how to render video to a [GLSurfaceView][] while applying
+a GL shader.
+
+The shader shows an overlap bitmap on top of the video. The overlay bitmap is
+drawn using an Android canvas, and includes the current frame's presentation
+timestamp, to show how to get the timestamp of the frame currently in the
+off-screen surface texture.
+
+[GLSurfaceView]: https://developer.android.com/reference/android/opengl/GLSurfaceView
diff --git a/tree/demos/gl/build.gradle b/tree/demos/gl/build.gradle
new file mode 100644
index 0000000..8fe3e04
--- /dev/null
+++ b/tree/demos/gl/build.gradle
@@ -0,0 +1,53 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+apply from: '../../constants.gradle'
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion project.ext.compileSdkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ versionName project.ext.releaseVersion
+ versionCode project.ext.releaseVersionCode
+ minSdkVersion project.ext.minSdkVersion
+ targetSdkVersion project.ext.appTargetSdkVersion
+ }
+
+ buildTypes {
+ release {
+ shrinkResources true
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+
+ lintOptions {
+ // This demo app does not have translations.
+ disable 'MissingTranslation'
+ }
+}
+
+dependencies {
+ implementation project(modulePrefix + 'library-core')
+ implementation project(modulePrefix + 'library-ui')
+ implementation project(modulePrefix + 'library-dash')
+ implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+}
diff --git a/tree/demos/gl/src/main/AndroidManifest.xml b/tree/demos/gl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4c95d1e
--- /dev/null
+++ b/tree/demos/gl/src/main/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.exoplayer2.gldemo">
+
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+ <uses-sdk/>
+
+ <application
+ android:allowBackup="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/application_name">
+
+ <activity
+ android:name=".MainActivity"
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.google.android.exoplayer.gldemo.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"/>
+ <data android:scheme="https"/>
+ <data android:scheme="content"/>
+ <data android:scheme="asset"/>
+ <data android:scheme="file"/>
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_fragment.glsl b/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_fragment.glsl
new file mode 100644
index 0000000..e54d0c2
--- /dev/null
+++ b/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_fragment.glsl
@@ -0,0 +1,35 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#extension GL_OES_EGL_image_external : require
+precision mediump float;
+// External texture containing video decoder output.
+uniform samplerExternalOES tex_sampler_0;
+// Texture containing the overlap bitmap.
+uniform sampler2D tex_sampler_1;
+// Horizontal scaling factor for the overlap bitmap.
+uniform float scaleX;
+// Vertical scaling factory for the overlap bitmap.
+uniform float scaleY;
+varying vec2 v_texcoord;
+void main() {
+ vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
+ vec4 overlayColor = texture2D(tex_sampler_1,
+ vec2(v_texcoord.x * scaleX,
+ v_texcoord.y * scaleY));
+ // Blend the video decoder output and the overlay bitmap.
+ gl_FragColor = videoColor * (1.0 - overlayColor.a)
+ + overlayColor * overlayColor.a;
+}
+
diff --git a/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_vertex.glsl b/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_vertex.glsl
new file mode 100644
index 0000000..e333d97
--- /dev/null
+++ b/tree/demos/gl/src/main/assets/bitmap_overlay_video_processor_vertex.glsl
@@ -0,0 +1,21 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+attribute vec4 a_position;
+attribute vec3 a_texcoord;
+varying vec2 v_texcoord;
+void main() {
+ gl_Position = a_position;
+ v_texcoord = a_texcoord.xy;
+}
+
diff --git a/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java
new file mode 100644
index 0000000..063b660
--- /dev/null
+++ b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/BitmapOverlayVideoProcessor.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.gldemo;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.GlUtil;
+import com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Video processor that demonstrates how to overlay a bitmap on video output using a GL shader. The
+ * bitmap is drawn using an Android {@link Canvas}.
+ */
+/* package */ final class BitmapOverlayVideoProcessor
+ implements VideoProcessingGLSurfaceView.VideoProcessor {
+
+ private static final int OVERLAY_WIDTH = 512;
+ private static final int OVERLAY_HEIGHT = 256;
+
+ private final Context context;
+ private final Paint paint;
+ private final int[] textures;
+ private final Bitmap overlayBitmap;
+ private final Bitmap logoBitmap;
+ private final Canvas overlayCanvas;
+
+ private int program;
+ @Nullable private GlUtil.Attribute[] attributes;
+ @Nullable private GlUtil.Uniform[] uniforms;
+
+ private float bitmapScaleX;
+ private float bitmapScaleY;
+
+ public BitmapOverlayVideoProcessor(Context context) {
+ this.context = context.getApplicationContext();
+ paint = new Paint();
+ paint.setTextSize(64);
+ paint.setAntiAlias(true);
+ paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
+ textures = new int[1];
+ overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
+ overlayCanvas = new Canvas(overlayBitmap);
+ try {
+ logoBitmap =
+ ((BitmapDrawable)
+ context.getPackageManager().getApplicationIcon(context.getPackageName()))
+ .getBitmap();
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void initialize() {
+ String vertexShaderCode =
+ loadAssetAsString(context, "bitmap_overlay_video_processor_vertex.glsl");
+ String fragmentShaderCode =
+ loadAssetAsString(context, "bitmap_overlay_video_processor_fragment.glsl");
+ program = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode);
+ GlUtil.Attribute[] attributes = GlUtil.getAttributes(program);
+ GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program);
+ for (GlUtil.Attribute attribute : attributes) {
+ if (attribute.name.equals("a_position")) {
+ attribute.setBuffer(
+ new float[] {
+ -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 0.0f, 1.0f,
+ },
+ 4);
+ } else if (attribute.name.equals("a_texcoord")) {
+ attribute.setBuffer(
+ new float[] {
+ 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
+ },
+ 3);
+ }
+ }
+ this.attributes = attributes;
+ this.uniforms = uniforms;
+ GLES20.glGenTextures(1, textures, 0);
+ GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
+ GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
+ GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+ GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
+ GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
+ GLUtils.texImage2D(GL10.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
+ }
+
+ @Override
+ public void setSurfaceSize(int width, int height) {
+ bitmapScaleX = (float) width / OVERLAY_WIDTH;
+ bitmapScaleY = (float) height / OVERLAY_HEIGHT;
+ }
+
+ @Override
+ public void draw(int frameTexture, long frameTimestampUs) {
+ // Draw to the canvas and store it in a texture.
+ String text = String.format(Locale.US, "%.02f", frameTimestampUs / (float) C.MICROS_PER_SECOND);
+ overlayBitmap.eraseColor(Color.TRANSPARENT);
+ overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint);
+ overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint);
+ GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
+ GLUtils.texSubImage2D(
+ GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
+ GlUtil.checkGlError();
+
+ // Run the shader program.
+ GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms);
+ GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes);
+ GLES20.glUseProgram(program);
+ for (GlUtil.Uniform uniform : uniforms) {
+ switch (uniform.name) {
+ case "tex_sampler_0":
+ uniform.setSamplerTexId(frameTexture, /* unit= */ 0);
+ break;
+ case "tex_sampler_1":
+ uniform.setSamplerTexId(textures[0], /* unit= */ 1);
+ break;
+ case "scaleX":
+ uniform.setFloat(bitmapScaleX);
+ break;
+ case "scaleY":
+ uniform.setFloat(bitmapScaleY);
+ break;
+ }
+ }
+ for (GlUtil.Attribute copyExternalAttribute : attributes) {
+ copyExternalAttribute.bind();
+ }
+ for (GlUtil.Uniform copyExternalUniform : uniforms) {
+ copyExternalUniform.bind();
+ }
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
+ GlUtil.checkGlError();
+ }
+
+ private static String loadAssetAsString(Context context, String assetFileName) {
+ @Nullable InputStream inputStream = null;
+ try {
+ inputStream = context.getAssets().open(assetFileName);
+ return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ Util.closeQuietly(inputStream);
+ }
+ }
+}
diff --git a/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/MainActivity.java b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/MainActivity.java
new file mode 100644
index 0000000..c788f75
--- /dev/null
+++ b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/MainActivity.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.gldemo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
+import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.EventLogger;
+import com.google.android.exoplayer2.util.GlUtil;
+import com.google.android.exoplayer2.util.Util;
+import java.util.UUID;
+
+/**
+ * Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
+ * postprocessing of the video content using GL.
+ */
+public final class MainActivity extends Activity {
+
+ private static final String TAG = "MainActivity";
+
+ private static final String DEFAULT_MEDIA_URI =
+ "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
+
+ private static final String ACTION_VIEW = "com.google.android.exoplayer.gldemo.action.VIEW";
+ private static final String EXTENSION_EXTRA = "extension";
+ private static final String DRM_SCHEME_EXTRA = "drm_scheme";
+ private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
+
+ @Nullable private PlayerView playerView;
+ @Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
+
+ @Nullable private SimpleExoPlayer player;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+ playerView = findViewById(R.id.player_view);
+
+ Context context = getApplicationContext();
+ boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
+ if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
+ Toast.makeText(
+ context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
+ new VideoProcessingGLSurfaceView(
+ context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
+ FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
+ contentFrame.addView(videoProcessingGLSurfaceView);
+ this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (Util.SDK_INT > 23) {
+ initializePlayer();
+ if (playerView != null) {
+ playerView.onResume();
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (Util.SDK_INT <= 23 || player == null) {
+ initializePlayer();
+ if (playerView != null) {
+ playerView.onResume();
+ }
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (Util.SDK_INT <= 23) {
+ if (playerView != null) {
+ playerView.onPause();
+ }
+ releasePlayer();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (Util.SDK_INT > 23) {
+ if (playerView != null) {
+ playerView.onPause();
+ }
+ releasePlayer();
+ }
+ }
+
+ private void initializePlayer() {
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ Uri uri =
+ ACTION_VIEW.equals(action)
+ ? Assertions.checkNotNull(intent.getData())
+ : Uri.parse(DEFAULT_MEDIA_URI);
+ String userAgent = Util.getUserAgent(this, getString(R.string.application_name));
+ DrmSessionManager drmSessionManager;
+ if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
+ String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
+ String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
+ UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
+ HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
+ HttpMediaDrmCallback drmCallback =
+ new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
+ drmSessionManager =
+ new DefaultDrmSessionManager.Builder()
+ .setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
+ .build(drmCallback);
+ } else {
+ drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
+ }
+
+ DataSource.Factory dataSourceFactory =
+ new DefaultDataSourceFactory(
+ this, Util.getUserAgent(this, getString(R.string.application_name)));
+ MediaSource mediaSource;
+ @C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
+ if (type == C.TYPE_DASH) {
+ mediaSource =
+ new DashMediaSource.Factory(dataSourceFactory)
+ .setDrmSessionManager(drmSessionManager)
+ .createMediaSource(uri);
+ } else if (type == C.TYPE_OTHER) {
+ mediaSource =
+ new ProgressiveMediaSource.Factory(dataSourceFactory)
+ .setDrmSessionManager(drmSessionManager)
+ .createMediaSource(uri);
+ } else {
+ throw new IllegalStateException();
+ }
+
+ SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
+ player.setRepeatMode(Player.REPEAT_MODE_ALL);
+ player.setMediaSource(mediaSource);
+ player.prepare();
+ player.play();
+ VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
+ Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
+ videoProcessingGLSurfaceView.setVideoComponent(
+ Assertions.checkNotNull(player.getVideoComponent()));
+ Assertions.checkNotNull(playerView).setPlayer(player);
+ player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
+ this.player = player;
+ }
+
+ private void releasePlayer() {
+ Assertions.checkNotNull(playerView).setPlayer(null);
+ if (player != null) {
+ player.release();
+ Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
+ player = null;
+ }
+ }
+}
diff --git a/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java
new file mode 100644
index 0000000..7aee748
--- /dev/null
+++ b/tree/demos/gl/src/main/java/com/google/android/exoplayer2/gldemo/VideoProcessingGLSurfaceView.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.gldemo;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaFormat;
+import android.opengl.EGL14;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.os.Handler;
+import android.view.Surface;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.GlUtil;
+import com.google.android.exoplayer2.util.TimedValueQueue;
+import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * {@link GLSurfaceView} that creates a GL context (optionally for protected content) and passes
+ * video frames to a {@link VideoProcessor} for drawing to the view.
+ *
+ * <p>This view must be created programmatically, as it is necessary to specify whether a context
+ * supporting protected content should be created at construction time.
+ */
+public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
+
+ /** Processes video frames, provided via a GL texture. */
+ public interface VideoProcessor {
+ /** Performs any required GL initialization. */
+ void initialize();
+
+ /** Sets the size of the output surface in pixels. */
+ void setSurfaceSize(int width, int height);
+
+ /**
+ * Draws using GL operations.
+ *
+ * @param frameTexture The ID of a GL texture containing a video frame.
+ * @param frameTimestampUs The presentation timestamp of the frame, in microseconds.
+ */
+ void draw(int frameTexture, long frameTimestampUs);
+ }
+
+ private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
+
+ private final VideoRenderer renderer;
+ private final Handler mainHandler;
+
+ @Nullable private SurfaceTexture surfaceTexture;
+ @Nullable private Surface surface;
+ @Nullable private Player.VideoComponent videoComponent;
+
+ /**
+ * Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link
+ * GLSurfaceView GLSurfaceView's} associated GL context should handle secure content (if the
+ * device supports it).
+ *
+ * @param context The {@link Context}.
+ * @param requireSecureContext Whether a GL context supporting protected content should be
+ * created, if supported by the device.
+ * @param videoProcessor Processor that draws to the view.
+ */
+ @SuppressWarnings("InlinedApi")
+ public VideoProcessingGLSurfaceView(
+ Context context, boolean requireSecureContext, VideoProcessor videoProcessor) {
+ super(context);
+ renderer = new VideoRenderer(videoProcessor);
+ mainHandler = new Handler();
+ setEGLContextClientVersion(2);
+ setEGLConfigChooser(
+ /* redSize= */ 8,
+ /* greenSize= */ 8,
+ /* blueSize= */ 8,
+ /* alphaSize= */ 8,
+ /* depthSize= */ 0,
+ /* stencilSize= */ 0);
+ setEGLContextFactory(
+ new EGLContextFactory() {
+ @Override
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+ int[] glAttributes;
+ if (requireSecureContext) {
+ glAttributes =
+ new int[] {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION,
+ 2,
+ EGL_PROTECTED_CONTENT_EXT,
+ EGL14.EGL_TRUE,
+ EGL14.EGL_NONE
+ };
+ } else {
+ glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
+ }
+ return egl.eglCreateContext(
+ display, eglConfig, /* share_context= */ EGL10.EGL_NO_CONTEXT, glAttributes);
+ }
+
+ @Override
+ public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+ egl.eglDestroyContext(display, context);
+ }
+ });
+ setEGLWindowSurfaceFactory(
+ new EGLWindowSurfaceFactory() {
+ @Override
+ public EGLSurface createWindowSurface(
+ EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
+ int[] attribsList =
+ requireSecureContext
+ ? new int[] {EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE}
+ : new int[] {EGL10.EGL_NONE};
+ return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList);
+ }
+
+ @Override
+ public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
+ egl.eglDestroySurface(display, surface);
+ }
+ });
+ setRenderer(renderer);
+ setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ }
+
+ /**
+ * Attaches or detaches (if {@code newVideoComponent} is {@code null}) this view from the video
+ * component of the player.
+ *
+ * @param newVideoComponent The new video component, or {@code null} to detach this view.
+ */
+ public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
+ if (newVideoComponent == videoComponent) {
+ return;
+ }
+ if (videoComponent != null) {
+ if (surface != null) {
+ videoComponent.clearVideoSurface(surface);
+ }
+ videoComponent.clearVideoFrameMetadataListener(renderer);
+ }
+ videoComponent = newVideoComponent;
+ if (videoComponent != null) {
+ videoComponent.setVideoFrameMetadataListener(renderer);
+ videoComponent.setVideoSurface(surface);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Post to make sure we occur in order with any onSurfaceTextureAvailable calls.
+ mainHandler.post(
+ () -> {
+ if (surface != null) {
+ if (videoComponent != null) {
+ videoComponent.setVideoSurface(null);
+ }
+ releaseSurface(surfaceTexture, surface);
+ surfaceTexture = null;
+ surface = null;
+ }
+ });
+ }
+
+ private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
+ mainHandler.post(
+ () -> {
+ SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
+ Surface oldSurface = VideoProcessingGLSurfaceView.this.surface;
+ this.surfaceTexture = surfaceTexture;
+ this.surface = new Surface(surfaceTexture);
+ releaseSurface(oldSurfaceTexture, oldSurface);
+ if (videoComponent != null) {
+ videoComponent.setVideoSurface(surface);
+ }
+ });
+ }
+
+ private static void releaseSurface(
+ @Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) {
+ if (oldSurfaceTexture != null) {
+ oldSurfaceTexture.release();
+ }
+ if (oldSurface != null) {
+ oldSurface.release();
+ }
+ }
+
+ private final class VideoRenderer implements GLSurfaceView.Renderer, VideoFrameMetadataListener {
+
+ private final VideoProcessor videoProcessor;
+ private final AtomicBoolean frameAvailable;
+ private final TimedValueQueue<Long> sampleTimestampQueue;
+
+ private int texture;
+ @Nullable private SurfaceTexture surfaceTexture;
+
+ private boolean initialized;
+ private int width;
+ private int height;
+ private long frameTimestampUs;
+
+ public VideoRenderer(VideoProcessor videoProcessor) {
+ this.videoProcessor = videoProcessor;
+ frameAvailable = new AtomicBoolean();
+ sampleTimestampQueue = new TimedValueQueue<>();
+ width = -1;
+ height = -1;
+ }
+
+ @Override
+ public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ texture = GlUtil.createExternalTexture();
+ surfaceTexture = new SurfaceTexture(texture);
+ surfaceTexture.setOnFrameAvailableListener(
+ surfaceTexture -> {
+ frameAvailable.set(true);
+ requestRender();
+ });
+ onSurfaceTextureAvailable(surfaceTexture);
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ GLES20.glViewport(0, 0, width, height);
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ if (videoProcessor == null) {
+ return;
+ }
+
+ if (!initialized) {
+ videoProcessor.initialize();
+ initialized = true;
+ }
+
+ if (width != -1 && height != -1) {
+ videoProcessor.setSurfaceSize(width, height);
+ width = -1;
+ height = -1;
+ }
+
+ if (frameAvailable.compareAndSet(true, false)) {
+ SurfaceTexture surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
+ surfaceTexture.updateTexImage();
+ long lastFrameTimestampNs = surfaceTexture.getTimestamp();
+ Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
+ if (frameTimestampUs != null) {
+ this.frameTimestampUs = frameTimestampUs;
+ }
+ }
+
+ videoProcessor.draw(texture, frameTimestampUs);
+ }
+
+ @Override
+ public void onVideoFrameAboutToBeRendered(
+ long presentationTimeUs,
+ long releaseTimeNs,
+ @NonNull Format format,
+ @Nullable MediaFormat mediaFormat) {
+ sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs);
+ }
+ }
+}
diff --git a/tree/demos/gl/src/main/res/layout/main_activity.xml b/tree/demos/gl/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..ec3868d
--- /dev/null
+++ b/tree/demos/gl/src/main/res/layout/main_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:keepScreenOn="true">
+
+ <com.google.android.exoplayer2.ui.PlayerView
+ android:id="@+id/player_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:surface_type="none"/>
+
+</FrameLayout>
+
diff --git a/tree/demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png b/tree/demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..adaa932
--- /dev/null
+++ b/tree/demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tree/demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png b/tree/demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9b6f7d5
--- /dev/null
+++ b/tree/demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tree/demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png b/tree/demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..2101026
--- /dev/null
+++ b/tree/demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tree/demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tree/demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..223ec8b
--- /dev/null
+++ b/tree/demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tree/demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tree/demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..698ed68
--- /dev/null
+++ b/tree/demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tree/demos/gl/src/main/res/values/strings.xml b/tree/demos/gl/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7e9e5d9
--- /dev/null
+++ b/tree/demos/gl/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <string name="application_name">ExoPlayer GL demo</string>
+
+ <string name="error_protected_content_extension_not_supported">The GL protected content extension is not supported.</string>
+
+</resources>
diff --git a/tree/demos/main/build.gradle b/tree/demos/main/build.gradle
index ab47b6d..b7a8666 100644
--- a/tree/demos/main/build.gradle
+++ b/tree/demos/main/build.gradle
@@ -64,7 +64,7 @@
dependencies {
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
- implementation 'com.google.android.material:material:1.0.0'
+ implementation 'com.google.android.material:material:1.1.0'
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-dash')
implementation project(modulePrefix + 'library-hls')
diff --git a/tree/demos/main/src/main/assets/media.exolist.json b/tree/demos/main/src/main/assets/media.exolist.json
index 06f063b..b9f3d63 100644
--- a/tree/demos/main/src/main/assets/media.exolist.json
+++ b/tree/demos/main/src/main/assets/media.exolist.json
@@ -616,5 +616,30 @@
"subtitle_language": "en"
}
]
+ },
+ {
+ "name": "60fps",
+ "samples": [
+ {
+ "name": "Big Buck Bunny (DASH,H264,1080p,Clear)",
+ "uri": "https://storage.googleapis.com/exoplayer-test-media-1/60fps/bbb-clear-1080/manifest.mpd"
+ },
+ {
+ "name": "Big Buck Bunny (DASH,H264,4K,Clear)",
+ "uri": "https://storage.googleapis.com/exoplayer-test-media-1/60fps/bbb-clear-2160/manifest.mpd"
+ },
+ {
+ "name": "Big Buck Bunny (DASH,H264,1080p,Widevine)",
+ "uri": "https://storage.googleapis.com/exoplayer-test-media-1/60fps/bbb-wv-1080/manifest.mpd",
+ "drm_scheme": "widevine",
+ "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
+ },
+ {
+ "name": "Big Buck Bunny (DASH,H264,4K,Widevine)",
+ "uri": "https://storage.googleapis.com/exoplayer-test-media-1/60fps/bbb-wv-2160/manifest.mpd",
+ "drm_scheme": "widevine",
+ "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
+ }
+ ]
}
]
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
index d83d707..bd74eb5 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
@@ -25,6 +25,7 @@
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
+import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
@@ -45,6 +46,8 @@
*/
public class DemoApplication extends Application {
+ public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
+
private static final String TAG = "DemoApplication";
private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
@@ -57,6 +60,7 @@
private Cache downloadCache;
private DownloadManager downloadManager;
private DownloadTracker downloadTracker;
+ private DownloadNotificationHelper downloadNotificationHelper;
@Override
public void onCreate() {
@@ -93,6 +97,14 @@
.setExtensionRendererMode(extensionRendererMode);
}
+ public DownloadNotificationHelper getDownloadNotificationHelper() {
+ if (downloadNotificationHelper == null) {
+ downloadNotificationHelper =
+ new DownloadNotificationHelper(this, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
+ }
+ return downloadNotificationHelper;
+ }
+
public DownloadManager getDownloadManager() {
initDownloadManager();
return downloadManager;
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
index be2863d..71b1eda 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
@@ -15,8 +15,11 @@
*/
package com.google.android.exoplayer2.demo;
+import static com.google.android.exoplayer2.demo.DemoApplication.DOWNLOAD_NOTIFICATION_CHANNEL_ID;
+
import android.app.Notification;
import android.content.Context;
+import androidx.annotation.NonNull;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadService;
@@ -29,35 +32,30 @@
/** A service for downloading media. */
public class DemoDownloadService extends DownloadService {
- private static final String CHANNEL_ID = "download_channel";
private static final int JOB_ID = 1;
private static final int FOREGROUND_NOTIFICATION_ID = 1;
- private DownloadNotificationHelper notificationHelper;
-
public DemoDownloadService() {
super(
FOREGROUND_NOTIFICATION_ID,
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
- CHANNEL_ID,
+ DOWNLOAD_NOTIFICATION_CHANNEL_ID,
R.string.exo_download_notification_channel_name,
/* channelDescriptionResourceId= */ 0);
}
@Override
- public void onCreate() {
- super.onCreate();
- notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID);
- }
-
- @Override
+ @NonNull
protected DownloadManager getDownloadManager() {
- DownloadManager downloadManager = ((DemoApplication) getApplication()).getDownloadManager();
// This will only happen once, because getDownloadManager is guaranteed to be called only once
// in the life cycle of the process.
+ DemoApplication application = (DemoApplication) getApplication();
+ DownloadManager downloadManager = application.getDownloadManager();
+ DownloadNotificationHelper downloadNotificationHelper =
+ application.getDownloadNotificationHelper();
downloadManager.addListener(
new TerminalStateNotificationHelper(
- this, notificationHelper, FOREGROUND_NOTIFICATION_ID + 1));
+ this, downloadNotificationHelper, FOREGROUND_NOTIFICATION_ID + 1));
return downloadManager;
}
@@ -67,9 +65,12 @@
}
@Override
- protected Notification getForegroundNotification(List<Download> downloads) {
- return notificationHelper.buildProgressNotification(
- R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloads);
+ @NonNull
+ protected Notification getForegroundNotification(@NonNull List<Download> downloads) {
+ return ((DemoApplication) getApplication())
+ .getDownloadNotificationHelper()
+ .buildProgressNotification(
+ R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloads);
}
/**
@@ -93,7 +94,7 @@
}
@Override
- public void onDownloadChanged(DownloadManager manager, Download download) {
+ public void onDownloadChanged(@NonNull DownloadManager manager, @NonNull Download download) {
Notification notification;
if (download.state == Download.STATE_COMPLETED) {
notification =
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
index 143eda9..be092e8 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
@@ -19,6 +19,7 @@
import android.content.DialogInterface;
import android.net.Uri;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.C;
@@ -141,7 +142,8 @@
private class DownloadManagerListener implements DownloadManager.Listener {
@Override
- public void onDownloadChanged(DownloadManager downloadManager, Download download) {
+ public void onDownloadChanged(
+ @NonNull DownloadManager downloadManager, @NonNull Download download) {
downloads.put(download.request.uri, download);
for (Listener listener : listeners) {
listener.onDownloadsChanged();
@@ -149,7 +151,8 @@
}
@Override
- public void onDownloadRemoved(DownloadManager downloadManager, Download download) {
+ public void onDownloadRemoved(
+ @NonNull DownloadManager downloadManager, @NonNull Download download) {
downloads.remove(download.request.uri);
for (Listener listener : listeners) {
listener.onDownloadsChanged();
@@ -187,7 +190,7 @@
// DownloadHelper.Callback implementation.
@Override
- public void onPrepared(DownloadHelper helper) {
+ public void onPrepared(@NonNull DownloadHelper helper) {
if (helper.getPeriodCount() == 0) {
Log.d(TAG, "No periods found. Downloading entire stream.");
startDownload();
@@ -214,7 +217,7 @@
}
@Override
- public void onPrepareError(DownloadHelper helper, IOException e) {
+ public void onPrepareError(@NonNull DownloadHelper helper, @NonNull IOException e) {
Toast.makeText(context, R.string.download_start_error, Toast.LENGTH_LONG).show();
Log.e(
TAG,
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
index 6aa5634..44d471b 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
@@ -32,36 +32,24 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.C.ContentType;
import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.demo.Sample.UriSample;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
-import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
-import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.MediaSourceFactory;
-import com.google.android.exoplayer2.source.MergingMediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
@@ -74,6 +62,7 @@
import com.google.android.exoplayer2.ui.spherical.SphericalGLSurfaceView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.Util;
@@ -136,6 +125,7 @@
private static final String KEY_AUTO_PLAY = "auto_play";
private static final CookieManager DEFAULT_COOKIE_MANAGER;
+
static {
DEFAULT_COOKIE_MANAGER = new CookieManager();
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
@@ -154,7 +144,7 @@
private DefaultTrackSelector.Parameters trackSelectorParameters;
private DebugTextViewHelper debugViewHelper;
private TrackGroupArray lastSeenTrackGroupArray;
-
+ private DefaultMediaSourceFactory mediaSourceFactory;
private boolean startAutoPlay;
private int startWindow;
private long startPosition;
@@ -175,6 +165,8 @@
}
super.onCreate(savedInstanceState);
dataSourceFactory = buildDataSourceFactory();
+ mediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(/* context= */ this, dataSourceFactory);
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
}
@@ -282,8 +274,9 @@
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length == 0) {
// Empty results are triggered if a permission is requested while another request was already
// pending and can be safely ignored in this case.
@@ -298,7 +291,7 @@
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
updateTrackSelectorParameters();
updateStartPosition();
@@ -381,6 +374,7 @@
.setTrackSelector(trackSelector)
.build();
player.addListener(new PlayerEventListener());
+ player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
player.setPlayWhenReady(startAutoPlay);
player.addAnalyticsListener(new EventLogger(trackSelector));
playerView.setPlayer(player);
@@ -415,47 +409,29 @@
? ((Sample.PlaylistSample) intentAsSample).children
: new UriSample[] {(UriSample) intentAsSample};
- boolean seenAdsTagUri = false;
+ List<MediaSource> mediaSources = new ArrayList<>();
+ Uri adTagUri = null;
for (UriSample sample : samples) {
- seenAdsTagUri |= sample.adTagUri != null;
- if (!Util.checkCleartextTrafficPermitted(sample.uri)) {
+ MediaItem mediaItem = sample.toMediaItem();
+ Assertions.checkNotNull(mediaItem.playbackProperties);
+ if (!Util.checkCleartextTrafficPermitted(mediaItem)) {
showToast(R.string.error_cleartext_not_permitted);
return Collections.emptyList();
}
- if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, sample.uri)) {
+ if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) {
// The player will be reinitialized if the permission is granted.
return Collections.emptyList();
}
+ MediaSource mediaSource = createLeafMediaSource(mediaItem);
+ if (mediaSource != null) {
+ adTagUri = sample.adTagUri;
+ mediaSources.add(mediaSource);
+ }
}
- List<MediaSource> mediaSources = new ArrayList<>();
- for (UriSample sample : samples) {
- MediaSource mediaSource = createLeafMediaSource(sample);
- if (mediaSource == null) {
- continue;
- }
- Sample.SubtitleInfo subtitleInfo = sample.subtitleInfo;
- if (subtitleInfo != null) {
- if (Util.maybeRequestReadExternalStoragePermission(
- /* activity= */ this, subtitleInfo.uri)) {
- // The player will be reinitialized if the permission is granted.
- return Collections.emptyList();
- }
- Format subtitleFormat =
- Format.createTextSampleFormat(
- /* id= */ null,
- subtitleInfo.mimeType,
- C.SELECTION_FLAG_DEFAULT,
- subtitleInfo.language);
- MediaSource subtitleMediaSource =
- new SingleSampleMediaSource.Factory(dataSourceFactory)
- .createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET);
- mediaSource = new MergingMediaSource(mediaSource, subtitleMediaSource);
- }
- mediaSources.add(mediaSource);
- }
- if (seenAdsTagUri && mediaSources.size() == 1) {
- Uri adTagUri = samples[0].adTagUri;
+ if (adTagUri == null) {
+ releaseAdsLoader();
+ } else if (mediaSources.size() == 1) {
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
@@ -466,92 +442,42 @@
} else {
showToast(R.string.ima_not_loaded);
}
- } else if (seenAdsTagUri && mediaSources.size() > 1) {
+ } else if (mediaSources.size() > 1) {
showToast(R.string.unsupported_ads_in_concatenation);
releaseAdsLoader();
- } else {
- releaseAdsLoader();
}
return mediaSources;
}
@Nullable
- private MediaSource createLeafMediaSource(UriSample parameters) {
- Sample.DrmInfo drmInfo = parameters.drmInfo;
- int errorStringId = R.string.error_drm_unknown;
- DrmSessionManager<ExoMediaCrypto> drmSessionManager = null;
- if (drmInfo == null) {
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
- } else if (Util.SDK_INT < 18) {
- errorStringId = R.string.error_drm_unsupported_before_api_18;
- } else if (!MediaDrm.isCryptoSchemeSupported(drmInfo.drmScheme)) {
- errorStringId = R.string.error_drm_unsupported_scheme;
- } else {
- MediaDrmCallback mediaDrmCallback =
- createMediaDrmCallback(drmInfo.drmLicenseUrl, drmInfo.drmKeyRequestProperties);
- drmSessionManager =
- new DefaultDrmSessionManager.Builder()
- .setUuidAndExoMediaDrmProvider(drmInfo.drmScheme, FrameworkMediaDrm.DEFAULT_PROVIDER)
- .setMultiSession(drmInfo.drmMultiSession)
- .setUseDrmSessionsForClearContent(drmInfo.drmSessionForClearTypes)
- .build(mediaDrmCallback);
- }
-
- if (drmSessionManager == null) {
- showToast(errorStringId);
- finish();
- return null;
+ private MediaSource createLeafMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
+ HttpDataSource.Factory drmDataSourceFactory = null;
+ if (mediaItem.playbackProperties.drmConfiguration != null) {
+ if (Util.SDK_INT < 18) {
+ showToast(R.string.error_drm_unsupported_before_api_18);
+ finish();
+ return null;
+ } else if (!MediaDrm.isCryptoSchemeSupported(
+ mediaItem.playbackProperties.drmConfiguration.uuid)) {
+ showToast(R.string.error_drm_unsupported_scheme);
+ finish();
+ return null;
+ }
+ drmDataSourceFactory = ((DemoApplication) getApplication()).buildHttpDataSourceFactory();
}
DownloadRequest downloadRequest =
((DemoApplication) getApplication())
.getDownloadTracker()
- .getDownloadRequest(parameters.uri);
+ .getDownloadRequest(mediaItem.playbackProperties.sourceUri);
if (downloadRequest != null) {
return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
}
- return createLeafMediaSource(parameters.uri, parameters.extension, drmSessionManager);
- }
-
- private MediaSource createLeafMediaSource(
- Uri uri, String extension, DrmSessionManager<?> drmSessionManager) {
- @ContentType int type = Util.inferContentType(uri, extension);
- switch (type) {
- case C.TYPE_DASH:
- return new DashMediaSource.Factory(dataSourceFactory)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- case C.TYPE_SS:
- return new SsMediaSource.Factory(dataSourceFactory)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- case C.TYPE_HLS:
- return new HlsMediaSource.Factory(dataSourceFactory)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- case C.TYPE_OTHER:
- return new ProgressiveMediaSource.Factory(dataSourceFactory)
- .setDrmSessionManager(drmSessionManager)
- .createMediaSource(uri);
- default:
- throw new IllegalStateException("Unsupported type: " + type);
- }
- }
-
- private HttpMediaDrmCallback createMediaDrmCallback(
- String licenseUrl, String[] keyRequestPropertiesArray) {
- HttpDataSource.Factory licenseDataSourceFactory =
- ((DemoApplication) getApplication()).buildHttpDataSourceFactory();
- HttpMediaDrmCallback drmCallback =
- new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
- if (keyRequestPropertiesArray != null) {
- for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
- drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
- keyRequestPropertiesArray[i + 1]);
- }
- }
- return drmCallback;
+ return mediaSourceFactory
+ .setDrmHttpDataSourceFactory(drmDataSourceFactory)
+ .createMediaSource(mediaItem);
}
private void releasePlayer() {
@@ -562,7 +488,7 @@
debugViewHelper = null;
player.release();
player = null;
- mediaSources = null;
+ mediaSources = Collections.emptyList();
trackSelector = null;
}
if (adsLoader != null) {
@@ -621,30 +547,7 @@
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
adsLoader = loaderConstructor.newInstance(this, adTagUri);
}
- MediaSourceFactory adMediaSourceFactory =
- new MediaSourceFactory() {
-
- private DrmSessionManager<?> drmSessionManager =
- DrmSessionManager.getDummyDrmSessionManager();
-
- @Override
- public MediaSourceFactory setDrmSessionManager(DrmSessionManager<?> drmSessionManager) {
- this.drmSessionManager = drmSessionManager;
- return this;
- }
-
- @Override
- public MediaSource createMediaSource(Uri uri) {
- return PlayerActivity.this.createLeafMediaSource(
- uri, /* extension=*/ null, drmSessionManager);
- }
-
- @Override
- public int[] getSupportedTypes() {
- return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
- }
- };
- return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView);
+ return new AdsMediaSource(mediaSource, mediaSourceFactory, adsLoader, playerView);
} catch (ClassNotFoundException e) {
// IMA extension not loaded.
return null;
@@ -689,7 +592,7 @@
private class PlayerEventListener implements Player.EventListener {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED) {
showControls();
}
@@ -697,7 +600,7 @@
}
@Override
- public void onPlayerError(ExoPlaybackException e) {
+ public void onPlayerError(@NonNull ExoPlaybackException e) {
if (isBehindLiveWindow(e)) {
clearStartPosition();
initializePlayer();
@@ -709,7 +612,8 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+ public void onTracksChanged(
+ @NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
updateButtonVisibility();
if (trackGroups != lastSeenTrackGroupArray) {
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
@@ -731,7 +635,8 @@
private class PlayerErrorMessageProvider implements ErrorMessageProvider<ExoPlaybackException> {
@Override
- public Pair<Integer, String> getErrorMessage(ExoPlaybackException e) {
+ @NonNull
+ public Pair<Integer, String> getErrorMessage(@NonNull ExoPlaybackException e) {
String errorString = getString(R.string.error_generic);
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java
index 0bf0d2a..1225c8b 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java
@@ -34,14 +34,44 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.UUID;
/* package */ abstract class Sample {
+ /**
+ * Returns the mime type which is one of {@link MimeTypes#APPLICATION_MPD} for DASH, {@link
+ * MimeTypes#APPLICATION_M3U8} for HLS, {@link MimeTypes#APPLICATION_SS} for SmoothStreaming or
+ * {@code null} for all other streams.
+ *
+ * @param uri The uri of the stream.
+ * @param extension The extension
+ * @return The adaptive mime type or {@code null} for non-adaptive streams.
+ */
+ @Nullable
+ public static String inferAdaptiveStreamMimeType(Uri uri, @Nullable String extension) {
+ @C.ContentType int contentType = Util.inferContentType(uri, extension);
+ switch (contentType) {
+ case C.TYPE_DASH:
+ return MimeTypes.APPLICATION_MPD;
+ case C.TYPE_HLS:
+ return MimeTypes.APPLICATION_M3U8;
+ case C.TYPE_SS:
+ return MimeTypes.APPLICATION_SS;
+ case C.TYPE_OTHER:
+ default:
+ return null;
+ }
+ }
+
public static final class UriSample extends Sample {
public static UriSample createFromIntent(Uri uri, Intent intent, String extrasKeySuffix) {
@@ -114,6 +144,35 @@
subtitleInfo.addToIntent(intent, extrasKeySuffix);
}
}
+
+ public MediaItem toMediaItem() {
+ MediaItem.Builder builder = new MediaItem.Builder().setSourceUri(uri);
+ builder.setMimeType(inferAdaptiveStreamMimeType(uri, extension));
+ if (drmInfo != null) {
+ Map<String, String> headers = new HashMap<>();
+ if (drmInfo.drmKeyRequestProperties != null) {
+ for (int i = 0; i < drmInfo.drmKeyRequestProperties.length; i += 2) {
+ headers.put(drmInfo.drmKeyRequestProperties[i], drmInfo.drmKeyRequestProperties[i + 1]);
+ }
+ }
+ builder
+ .setDrmLicenseUri(drmInfo.drmLicenseUrl)
+ .setDrmLicenseRequestHeaders(headers)
+ .setDrmUuid(drmInfo.drmScheme)
+ .setDrmMultiSession(drmInfo.drmMultiSession)
+ .setDrmSessionForClearTypes(Util.toList(drmInfo.drmSessionForClearTypes));
+ }
+ if (subtitleInfo != null) {
+ builder.setSubtitles(
+ Collections.singletonList(
+ new MediaItem.Subtitle(
+ subtitleInfo.uri,
+ subtitleInfo.mimeType,
+ subtitleInfo.language,
+ C.SELECTION_FLAG_DEFAULT)));
+ }
+ return builder.build();
+ }
}
public static final class PlaylistSample extends Sample {
@@ -267,7 +326,7 @@
}
}
- @Nullable public final String name;
+ public final String name;
public Sample(String name) {
this.name = name;
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
index 66bf4ba..ebfaa76 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
@@ -17,6 +17,8 @@
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.AsyncTask;
@@ -34,6 +36,7 @@
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.ParserException;
@@ -62,26 +65,30 @@
implements DownloadTracker.Listener, OnChildClickListener {
private static final String TAG = "SampleChooserActivity";
+ private static final String GROUP_POSITION_PREFERENCE_KEY = "SAMPLE_CHOOSER_GROUP_POSITION";
+ private static final String CHILD_POSITION_PREFERENCE_KEY = "SAMPLE_CHOOSER_CHILD_POSITION";
+ private String[] uris;
private boolean useExtensionRenderers;
private DownloadTracker downloadTracker;
private SampleAdapter sampleAdapter;
private MenuItem preferExtensionDecodersMenuItem;
private MenuItem randomAbrMenuItem;
private MenuItem tunnelingMenuItem;
+ private ExpandableListView sampleListView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_chooser_activity);
sampleAdapter = new SampleAdapter();
- ExpandableListView sampleListView = findViewById(R.id.sample_list);
+ sampleListView = findViewById(R.id.sample_list);
+
sampleListView.setAdapter(sampleAdapter);
sampleListView.setOnChildClickListener(this);
Intent intent = getIntent();
String dataUri = intent.getDataString();
- String[] uris;
if (dataUri != null) {
uris = new String[] {dataUri};
} else {
@@ -105,8 +112,7 @@
DemoApplication application = (DemoApplication) getApplication();
useExtensionRenderers = application.useExtensionRenderers();
downloadTracker = application.getDownloadTracker();
- SampleListLoader loaderTask = new SampleListLoader();
- loaderTask.execute(uris);
+ loadSample();
// Start the download service if it should be running but it's not currently.
// Starting the service in the foreground causes notification flicker if there is no scheduled
@@ -157,17 +163,70 @@
sampleAdapter.notifyDataSetChanged();
}
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults.length == 0) {
+ // Empty results are triggered if a permission is requested while another request was already
+ // pending and can be safely ignored in this case.
+ return;
+ }
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ loadSample();
+ } else {
+ Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
+ .show();
+ finish();
+ }
+ }
+
+ private void loadSample() {
+ Assertions.checkNotNull(uris);
+
+ for (int i = 0; i < uris.length; i++) {
+ Uri uri = Uri.parse(uris[i]);
+ if (Util.maybeRequestReadExternalStoragePermission(this, uri)) {
+ return;
+ }
+ }
+
+ SampleListLoader loaderTask = new SampleListLoader();
+ loaderTask.execute(uris);
+ }
+
private void onSampleGroups(final List<SampleGroup> groups, boolean sawError) {
if (sawError) {
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
.show();
}
sampleAdapter.setSampleGroups(groups);
+
+ SharedPreferences preferences = getPreferences(MODE_PRIVATE);
+
+ int groupPosition = -1;
+ int childPosition = -1;
+ try {
+ groupPosition = preferences.getInt(GROUP_POSITION_PREFERENCE_KEY, /* defValue= */ -1);
+ childPosition = preferences.getInt(CHILD_POSITION_PREFERENCE_KEY, /* defValue= */ -1);
+ } catch (ClassCastException e) {
+ Log.w(TAG, "Saved position is not an int. Will not restore position.", e);
+ }
+ if (groupPosition != -1 && childPosition != -1) {
+ sampleListView.expandGroup(groupPosition); // shouldExpandGroup does not work without this.
+ sampleListView.setSelectedChild(groupPosition, childPosition, /* shouldExpandGroup= */ true);
+ }
}
@Override
public boolean onChildClick(
ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
+ // Save the selected item first to be able to restore it if the tested code crashes.
+ SharedPreferences.Editor prefEditor = getPreferences(MODE_PRIVATE).edit();
+ prefEditor.putInt(GROUP_POSITION_PREFERENCE_KEY, groupPosition);
+ prefEditor.putInt(CHILD_POSITION_PREFERENCE_KEY, childPosition);
+ prefEditor.apply();
+
Sample sample = (Sample) view.getTag();
Intent intent = new Intent(this, PlayerActivity.class);
intent.putExtra(
@@ -435,7 +494,6 @@
groups.add(group);
return group;
}
-
}
private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener {
@@ -462,8 +520,12 @@
}
@Override
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
+ public View getChildView(
+ int groupPosition,
+ int childPosition,
+ boolean isLastChild,
+ View convertView,
+ ViewGroup parent) {
View view = convertView;
if (view == null) {
view = getLayoutInflater().inflate(R.layout.sample_list_item, parent, false);
@@ -491,8 +553,8 @@
}
@Override
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
+ public View getGroupView(
+ int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view =
@@ -548,6 +610,5 @@
this.title = title;
this.samples = new ArrayList<>();
}
-
}
}
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java
index 9e80093..b1db441 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java
@@ -24,6 +24,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDialog;
import androidx.fragment.app.DialogFragment;
@@ -212,6 +213,7 @@
}
@Override
+ @NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
// We need to own the view to let tab layout work correctly on all API levels. We can't use
// AlertDialog because it owns the view itself, so we use AppCompatDialog instead, themed using
@@ -223,16 +225,14 @@
}
@Override
- public void onDismiss(DialogInterface dialog) {
+ public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
onDismissListener.onDismiss(dialog);
}
- @Nullable
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-
View dialogView = inflater.inflate(R.layout.track_selection_dialog, container, false);
TabLayout tabLayout = dialogView.findViewById(R.id.track_selection_dialog_tab_layout);
ViewPager viewPager = dialogView.findViewById(R.id.track_selection_dialog_view_pager);
@@ -290,6 +290,7 @@
}
@Override
+ @NonNull
public Fragment getItem(int position) {
return tabFragments.valueAt(position);
}
@@ -299,7 +300,6 @@
return tabFragments.size();
}
- @Nullable
@Override
public CharSequence getPageTitle(int position) {
return getTrackTypeString(getResources(), tabTrackTypes.get(position));
@@ -341,7 +341,6 @@
this.allowMultipleOverrides = allowMultipleOverrides;
}
- @Nullable
@Override
public View onCreateView(
LayoutInflater inflater,
@@ -360,7 +359,8 @@
}
@Override
- public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) {
+ public void onTrackSelectionChanged(
+ boolean isDisabled, @NonNull List<SelectionOverride> overrides) {
this.isDisabled = isDisabled;
this.overrides = overrides;
}
diff --git a/tree/demos/surface/src/main/java/com/google/android/exoplayer2/surfacedemo/MainActivity.java b/tree/demos/surface/src/main/java/com/google/android/exoplayer2/surfacedemo/MainActivity.java
index 402a71e..67419ed 100644
--- a/tree/demos/surface/src/main/java/com/google/android/exoplayer2/surfacedemo/MainActivity.java
+++ b/tree/demos/surface/src/main/java/com/google/android/exoplayer2/surfacedemo/MainActivity.java
@@ -32,7 +32,6 @@
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.source.MediaSource;
@@ -185,7 +184,7 @@
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
String userAgent = Util.getUserAgent(this, getString(R.string.application_name));
- DrmSessionManager<ExoMediaCrypto> drmSessionManager;
+ DrmSessionManager drmSessionManager;
if (intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
@@ -220,7 +219,8 @@
throw new IllegalStateException();
}
SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
- player.prepare(mediaSource);
+ player.setMediaSource(mediaSource);
+ player.prepare();
player.play();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
diff --git a/tree/extensions/av1/build.gradle b/tree/extensions/av1/build.gradle
index 0b539d5..d61a3a9 100644
--- a/tree/extensions/av1/build.gradle
+++ b/tree/extensions/av1/build.gradle
@@ -65,6 +65,7 @@
dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1Decoder.java b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1Decoder.java
index 687ac47..8837d0e 100644
--- a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1Decoder.java
+++ b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1Decoder.java
@@ -88,8 +88,8 @@
return new VideoDecoderOutputBuffer(this::releaseOutputBuffer);
}
- @Nullable
@Override
+ @Nullable
protected Gav1DecoderException decode(
VideoDecoderInputBuffer inputBuffer, VideoDecoderOutputBuffer outputBuffer, boolean reset) {
ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
@@ -203,7 +203,7 @@
* @param context Decoder context.
* @param surface Output surface.
* @param outputBuffer Output buffer with the decoded frame.
- * @return {@link #GAV1_OK} if successful, {@link #GAV1_ERROR} if an error occured.
+ * @return {@link #GAV1_OK} if successful, {@link #GAV1_ERROR} if an error occurred.
*/
private native int gav1RenderFrame(
long context, Surface surface, VideoDecoderOutputBuffer outputBuffer);
@@ -225,10 +225,10 @@
private native String gav1GetErrorMessage(long context);
/**
- * Returns whether an error occured.
+ * Returns whether an error occurred.
*
* @param context Decoder context.
- * @return {@link #GAV1_OK} if there was no error, {@link #GAV1_ERROR} if an error occured.
+ * @return {@link #GAV1_OK} if there was no error, {@link #GAV1_ERROR} if an error occurred.
*/
private native int gav1CheckError(long context);
}
diff --git a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1DecoderException.java b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1DecoderException.java
index 9d8692c..13839f0 100644
--- a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1DecoderException.java
+++ b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Gav1DecoderException.java
@@ -15,10 +15,10 @@
*/
package com.google.android.exoplayer2.ext.av1;
-import com.google.android.exoplayer2.video.VideoDecoderException;
+import com.google.android.exoplayer2.decoder.DecoderException;
/** Thrown when a libgav1 decoder error occurs. */
-public final class Gav1DecoderException extends VideoDecoderException {
+public final class Gav1DecoderException extends DecoderException {
/* package */ Gav1DecoderException(String message) {
super(message);
diff --git a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java
index fc4d527..0a37338 100644
--- a/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java
+++ b/tree/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java
@@ -21,40 +21,20 @@
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
-import com.google.android.exoplayer2.video.SimpleDecoderVideoRenderer;
-import com.google.android.exoplayer2.video.VideoDecoderException;
-import com.google.android.exoplayer2.video.VideoDecoderInputBuffer;
+import com.google.android.exoplayer2.video.DecoderVideoRenderer;
import com.google.android.exoplayer2.video.VideoDecoderOutputBuffer;
-import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
-/**
- * Decodes and renders video using libgav1 decoder.
- *
- * <p>This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)}
- * on the playback thread:
- *
- * <ul>
- * <li>Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload
- * should be the target {@link Surface}, or null.
- * <li>Message with type {@link #MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER} to set the output
- * buffer renderer. The message payload should be the target {@link
- * VideoDecoderOutputBufferRenderer}, or null.
- * </ul>
- */
-public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
+/** Decodes and renders video using libgav1 decoder. */
+public class Libgav1VideoRenderer extends DecoderVideoRenderer {
+ private static final String TAG = "Libgav1VideoRenderer";
private static final int DEFAULT_NUM_OF_INPUT_BUFFERS = 4;
private static final int DEFAULT_NUM_OF_OUTPUT_BUFFERS = 4;
/* Default size based on 720p resolution video compressed by a factor of two. */
@@ -74,7 +54,7 @@
@Nullable private Gav1Decoder decoder;
/**
- * Creates a Libgav1VideoRenderer.
+ * Creates a new instance.
*
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
@@ -100,7 +80,7 @@
}
/**
- * Creates a Libgav1VideoRenderer.
+ * Creates a new instance.
*
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
@@ -121,39 +101,33 @@
int threads,
int numInputBuffers,
int numOutputBuffers) {
- super(
- allowedJoiningTimeMs,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false);
+ super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
this.threads = threads;
this.numInputBuffers = numInputBuffers;
this.numOutputBuffers = numOutputBuffers;
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
+ public final int supportsFormat(Format format) {
if (!MimeTypes.VIDEO_AV1.equalsIgnoreCase(format.sampleMimeType)
|| !Gav1Library.isAvailable()) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
}
- if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
+ if (format.drmInitData != null && format.exoMediaCryptoType == null) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM);
}
return RendererCapabilities.create(FORMAT_HANDLED, ADAPTIVE_SEAMLESS, TUNNELING_NOT_SUPPORTED);
}
@Override
- protected SimpleDecoder<
- VideoDecoderInputBuffer,
- ? extends VideoDecoderOutputBuffer,
- ? extends VideoDecoderException>
- createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
- throws VideoDecoderException {
+ protected Gav1Decoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
+ throws Gav1DecoderException {
TraceUtil.beginSection("createGav1Decoder");
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
@@ -181,17 +155,4 @@
decoder.setOutputMode(outputMode);
}
}
-
- // PlayerMessage.Target implementation.
-
- @Override
- public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
- if (messageType == MSG_SET_SURFACE) {
- setOutputSurface((Surface) message);
- } else if (messageType == MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) {
- setOutputBufferRenderer((VideoDecoderOutputBufferRenderer) message);
- } else {
- super.handleMessage(messageType, message);
- }
- }
}
diff --git a/tree/extensions/av1/src/main/jni/gav1_jni.cc b/tree/extensions/av1/src/main/jni/gav1_jni.cc
index 9ac3ea5..078ecdc 100644
--- a/tree/extensions/av1/src/main/jni/gav1_jni.cc
+++ b/tree/extensions/av1/src/main/jni/gav1_jni.cc
@@ -27,6 +27,7 @@
#endif // CPU_FEATURES_COMPILED_ANY_ARM_NEON
#include <jni.h>
+#include <cstdint>
#include <cstring>
#include <mutex> // NOLINT
#include <new>
@@ -71,7 +72,7 @@
// Output modes.
const int kOutputModeYuv = 0;
const int kOutputModeSurfaceYuv = 1;
-// LINT.ThenChange(../../../../../library/core/src/main/java/com/google/android/exoplayer2/C.java)
+// LINT.ThenChange(../../../../../library/common/src/main/java/com/google/android/exoplayer2/C.java)
// LINT.IfChange
const int kColorSpaceUnknown = 0;
@@ -121,18 +122,22 @@
}
}
-// Manages Libgav1FrameBuffer and reference information.
+// Manages frame buffer and reference information.
class JniFrameBuffer {
public:
- explicit JniFrameBuffer(int id) : id_(id), reference_count_(0) {
- gav1_frame_buffer_.private_data = &id_;
- }
+ explicit JniFrameBuffer(int id) : id_(id), reference_count_(0) {}
~JniFrameBuffer() {
for (int plane_index = kPlaneY; plane_index < kMaxPlanes; plane_index++) {
- delete[] gav1_frame_buffer_.data[plane_index];
+ delete[] raw_buffer_[plane_index];
}
}
+ // Not copyable or movable.
+ JniFrameBuffer(const JniFrameBuffer&) = delete;
+ JniFrameBuffer(JniFrameBuffer&&) = delete;
+ JniFrameBuffer& operator=(const JniFrameBuffer&) = delete;
+ JniFrameBuffer& operator=(JniFrameBuffer&&) = delete;
+
void SetFrameData(const libgav1::DecoderBuffer& decoder_buffer) {
for (int plane_index = kPlaneY; plane_index < decoder_buffer.NumPlanes();
plane_index++) {
@@ -160,9 +165,8 @@
void RemoveReference() { reference_count_--; }
bool InUse() const { return reference_count_ != 0; }
- const Libgav1FrameBuffer& GetGav1FrameBuffer() const {
- return gav1_frame_buffer_;
- }
+ uint8_t* RawBuffer(int plane_index) const { return raw_buffer_[plane_index]; }
+ void* BufferPrivateData() const { return const_cast<int*>(&id_); }
// Attempts to reallocate data planes if the existing ones don't have enough
// capacity. Returns true if the allocation was successful or wasn't needed,
@@ -172,15 +176,14 @@
for (int plane_index = kPlaneY; plane_index < kMaxPlanes; plane_index++) {
const int min_size =
(plane_index == kPlaneY) ? y_plane_min_size : uv_plane_min_size;
- if (gav1_frame_buffer_.size[plane_index] >= min_size) continue;
- delete[] gav1_frame_buffer_.data[plane_index];
- gav1_frame_buffer_.data[plane_index] =
- new (std::nothrow) uint8_t[min_size];
- if (!gav1_frame_buffer_.data[plane_index]) {
- gav1_frame_buffer_.size[plane_index] = 0;
+ if (raw_buffer_size_[plane_index] >= min_size) continue;
+ delete[] raw_buffer_[plane_index];
+ raw_buffer_[plane_index] = new (std::nothrow) uint8_t[min_size];
+ if (!raw_buffer_[plane_index]) {
+ raw_buffer_size_[plane_index] = 0;
return false;
}
- gav1_frame_buffer_.size[plane_index] = min_size;
+ raw_buffer_size_[plane_index] = min_size;
}
return true;
}
@@ -190,9 +193,12 @@
uint8_t* plane_[kMaxPlanes];
int displayed_width_[kMaxPlanes];
int displayed_height_[kMaxPlanes];
- int id_;
+ const int id_;
int reference_count_;
- Libgav1FrameBuffer gav1_frame_buffer_ = {};
+ // Pointers to the raw buffers allocated for the data planes.
+ uint8_t* raw_buffer_[kMaxPlanes] = {};
+ // Sizes of the raw buffers in bytes.
+ size_t raw_buffer_size_[kMaxPlanes] = {};
};
// Manages frame buffers used by libgav1 decoder and ExoPlayer.
@@ -210,7 +216,7 @@
}
JniStatusCode GetBuffer(size_t y_plane_min_size, size_t uv_plane_min_size,
- Libgav1FrameBuffer* frame_buffer) {
+ JniFrameBuffer** jni_buffer) {
std::lock_guard<std::mutex> lock(mutex_);
JniFrameBuffer* output_buffer;
@@ -230,7 +236,7 @@
}
output_buffer->AddReference();
- *frame_buffer = output_buffer->GetGav1FrameBuffer();
+ *jni_buffer = output_buffer;
return kJniStatusOk;
}
@@ -316,29 +322,46 @@
JniStatusCode jni_status_code = kJniStatusOk;
};
-int Libgav1GetFrameBuffer(void* private_data, size_t y_plane_min_size,
- size_t uv_plane_min_size,
- Libgav1FrameBuffer* frame_buffer) {
- JniContext* const context = reinterpret_cast<JniContext*>(private_data);
+Libgav1StatusCode Libgav1GetFrameBuffer(void* callback_private_data,
+ int bitdepth,
+ libgav1::ImageFormat image_format,
+ int width, int height, int left_border,
+ int right_border, int top_border,
+ int bottom_border, int stride_alignment,
+ libgav1::FrameBuffer* frame_buffer) {
+ libgav1::FrameBufferInfo info;
+ Libgav1StatusCode status = libgav1::ComputeFrameBufferInfo(
+ bitdepth, image_format, width, height, left_border, right_border,
+ top_border, bottom_border, stride_alignment, &info);
+ if (status != kLibgav1StatusOk) return status;
+
+ JniContext* const context = static_cast<JniContext*>(callback_private_data);
+ JniFrameBuffer* jni_buffer;
context->jni_status_code = context->buffer_manager.GetBuffer(
- y_plane_min_size, uv_plane_min_size, frame_buffer);
+ info.y_buffer_size, info.uv_buffer_size, &jni_buffer);
if (context->jni_status_code != kJniStatusOk) {
LOGE("%s", GetJniErrorMessage(context->jni_status_code));
- return -1;
+ return kLibgav1StatusOutOfMemory;
}
- return 0;
+
+ uint8_t* const y_buffer = jni_buffer->RawBuffer(0);
+ uint8_t* const u_buffer =
+ (info.uv_buffer_size != 0) ? jni_buffer->RawBuffer(1) : nullptr;
+ uint8_t* const v_buffer =
+ (info.uv_buffer_size != 0) ? jni_buffer->RawBuffer(2) : nullptr;
+
+ return libgav1::SetFrameBuffer(&info, y_buffer, u_buffer, v_buffer,
+ jni_buffer->BufferPrivateData(), frame_buffer);
}
-int Libgav1ReleaseFrameBuffer(void* private_data,
- Libgav1FrameBuffer* frame_buffer) {
- JniContext* const context = reinterpret_cast<JniContext*>(private_data);
- const int buffer_id = *reinterpret_cast<int*>(frame_buffer->private_data);
+void Libgav1ReleaseFrameBuffer(void* callback_private_data,
+ void* buffer_private_data) {
+ JniContext* const context = static_cast<JniContext*>(callback_private_data);
+ const int buffer_id = *static_cast<const int*>(buffer_private_data);
context->jni_status_code = context->buffer_manager.ReleaseBuffer(buffer_id);
if (context->jni_status_code != kJniStatusOk) {
LOGE("%s", GetJniErrorMessage(context->jni_status_code));
- return -1;
}
- return 0;
}
constexpr int AlignTo16(int value) { return (value + 15) & (~15); }
@@ -508,8 +531,8 @@
libgav1::DecoderSettings settings;
settings.threads = threads;
- settings.get = Libgav1GetFrameBuffer;
- settings.release = Libgav1ReleaseFrameBuffer;
+ settings.get_frame_buffer = Libgav1GetFrameBuffer;
+ settings.release_frame_buffer = Libgav1ReleaseFrameBuffer;
settings.callback_private_data = context;
context->libgav1_status_code = context->decoder.Init(&settings);
@@ -544,7 +567,8 @@
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(
env->GetDirectBufferAddress(encodedData));
context->libgav1_status_code =
- context->decoder.EnqueueFrame(buffer, length, /*user_private_data=*/0);
+ context->decoder.EnqueueFrame(buffer, length, /*user_private_data=*/0,
+ /*buffer_private_data=*/nullptr);
if (context->libgav1_status_code != kLibgav1StatusOk) {
return kStatusError;
}
@@ -619,7 +643,7 @@
}
const int buffer_id =
- *reinterpret_cast<int*>(decoder_buffer->buffer_private_data);
+ *static_cast<const int*>(decoder_buffer->buffer_private_data);
context->buffer_manager.AddBufferReference(buffer_id);
JniFrameBuffer* const jni_buffer =
context->buffer_manager.GetBuffer(buffer_id);
diff --git a/tree/extensions/cast/build.gradle b/tree/extensions/cast/build.gradle
index 0d7d96d..853861e 100644
--- a/tree/extensions/cast/build.gradle
+++ b/tree/extensions/cast/build.gradle
@@ -31,12 +31,13 @@
}
dependencies {
- api 'com.google.android.gms:play-services-cast-framework:17.0.0'
+ api 'com.google.android.gms:play-services-cast-framework:18.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
index 5b91410..835d6a3 100644
--- a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
+++ b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
@@ -21,6 +21,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
@@ -83,6 +84,7 @@
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
private final CastContext castContext;
+ private final MediaItemConverter mediaItemConverter;
// TODO: Allow custom implementations of CastTimelineTracker.
private final CastTimelineTracker timelineTracker;
private final Timeline.Period period;
@@ -112,10 +114,23 @@
private long pendingSeekPositionMs;
/**
+ * Creates a new cast player that uses a {@link DefaultMediaItemConverter}.
+ *
* @param castContext The context from which the cast session is obtained.
*/
public CastPlayer(CastContext castContext) {
+ this(castContext, new DefaultMediaItemConverter());
+ }
+
+ /**
+ * Creates a new cast player.
+ *
+ * @param castContext The context from which the cast session is obtained.
+ * @param mediaItemConverter The {@link MediaItemConverter} to use.
+ */
+ public CastPlayer(CastContext castContext, MediaItemConverter mediaItemConverter) {
this.castContext = castContext;
+ this.mediaItemConverter = mediaItemConverter;
timelineTracker = new CastTimelineTracker();
period = new Timeline.Period();
statusListener = new StatusListener();
@@ -142,105 +157,61 @@
// Media Queue manipulation methods.
- /**
- * Loads a single item media queue. If no session is available, does nothing.
- *
- * @param item The item to load.
- * @param positionMs The position at which the playback should start in milliseconds relative to
- * the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
- * starts at position 0.
- * @return The Cast {@code PendingResult}, or null if no session is available.
- */
+ /** @deprecated Use {@link #setMediaItems(List, int, long)} instead. */
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> loadItem(MediaQueueItem item, long positionMs) {
- return loadItems(new MediaQueueItem[] {item}, 0, positionMs, REPEAT_MODE_OFF);
+ return setMediaItemsInternal(
+ new MediaQueueItem[] {item}, /* startWindowIndex= */ 0, positionMs, repeatMode.value);
}
/**
- * Loads a media queue. If no session is available, does nothing.
- *
- * @param items The items to load.
- * @param startIndex The index of the item at which playback should start.
- * @param positionMs The position at which the playback should start in milliseconds relative to
- * the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
- * starts at position 0.
- * @param repeatMode The repeat mode for the created media queue.
- * @return The Cast {@code PendingResult}, or null if no session is available.
+ * @deprecated Use {@link #setMediaItems(List, int, long)} and {@link #setRepeatMode(int)}
+ * instead.
*/
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> loadItems(
MediaQueueItem[] items, int startIndex, long positionMs, @RepeatMode int repeatMode) {
- if (remoteMediaClient != null) {
- positionMs = positionMs != C.TIME_UNSET ? positionMs : 0;
- return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
- positionMs, null);
- }
- return null;
+ return setMediaItemsInternal(items, startIndex, positionMs, repeatMode);
}
- /**
- * Appends a sequence of items to the media queue. If no media queue exists, does nothing.
- *
- * @param items The items to append.
- * @return The Cast {@code PendingResult}, or null if no media queue exists.
- */
+ /** @deprecated Use {@link #addMediaItems(List)} instead. */
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> addItems(MediaQueueItem... items) {
- return addItems(MediaQueueItem.INVALID_ITEM_ID, items);
+ return addMediaItemsInternal(items, MediaQueueItem.INVALID_ITEM_ID);
}
- /**
- * Inserts a sequence of items into the media queue. If no media queue or period with id {@code
- * periodId} exist, does nothing.
- *
- * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
- * that will follow immediately after the inserted items.
- * @param items The items to insert.
- * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
- * periodId} exist.
- */
+ /** @deprecated Use {@link #addMediaItems(int, List)} instead. */
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> addItems(int periodId, MediaQueueItem... items) {
- if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID
- || currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET)) {
- return remoteMediaClient.queueInsertItems(items, periodId, null);
+ if (periodId == MediaQueueItem.INVALID_ITEM_ID
+ || currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
+ return addMediaItemsInternal(items, periodId);
}
return null;
}
- /**
- * Removes an item from the media queue. If no media queue or period with id {@code periodId}
- * exist, does nothing.
- *
- * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
- * to remove.
- * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
- * periodId} exist.
- */
+ /** @deprecated Use {@link #removeMediaItem(int)} instead. */
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> removeItem(int periodId) {
- if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
- return remoteMediaClient.queueRemoveItem(periodId, null);
+ if (currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
+ return removeMediaItemsInternal(new int[] {periodId});
}
return null;
}
- /**
- * Moves an existing item within the media queue. If no media queue or period with id {@code
- * periodId} exist, does nothing.
- *
- * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
- * to move.
- * @param newIndex The target index of the item in the media queue. Must be in the range 0 <=
- * index < {@link Timeline#getPeriodCount()}, as provided by {@link #getCurrentTimeline()}.
- * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
- * periodId} exist.
- */
+ /** @deprecated Use {@link #moveMediaItem(int, int)} instead. */
+ @Deprecated
@Nullable
public PendingResult<MediaChannelResult> moveItem(int periodId, int newIndex) {
- Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount());
- if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
- return remoteMediaClient.queueMoveItemToNewIndex(periodId, newIndex, null);
+ Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getWindowCount());
+ int fromIndex = currentTimeline.getIndexOfPeriod(periodId);
+ if (fromIndex != C.INDEX_UNSET && fromIndex != newIndex) {
+ return moveMediaItemsInternal(new int[] {periodId}, fromIndex, newIndex);
}
return null;
}
@@ -306,6 +277,13 @@
}
@Override
+ @Nullable
+ public DeviceComponent getDeviceComponent() {
+ // TODO(b/151792305): Implement the component.
+ return null;
+ }
+
+ @Override
public Looper getApplicationLooper() {
return Looper.getMainLooper();
}
@@ -326,6 +304,73 @@
}
@Override
+ public void setMediaItems(
+ List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs) {
+ setMediaItemsInternal(
+ toMediaQueueItems(mediaItems), startWindowIndex, startPositionMs, repeatMode.value);
+ }
+
+ @Override
+ public void addMediaItems(List<MediaItem> mediaItems) {
+ addMediaItemsInternal(toMediaQueueItems(mediaItems), MediaQueueItem.INVALID_ITEM_ID);
+ }
+
+ @Override
+ public void addMediaItems(int index, List<MediaItem> mediaItems) {
+ Assertions.checkArgument(index >= 0);
+ int uid = MediaQueueItem.INVALID_ITEM_ID;
+ if (index < currentTimeline.getWindowCount()) {
+ uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid;
+ }
+ addMediaItemsInternal(toMediaQueueItems(mediaItems), uid);
+ }
+
+ @Override
+ public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
+ Assertions.checkArgument(
+ fromIndex >= 0
+ && fromIndex <= toIndex
+ && toIndex <= currentTimeline.getWindowCount()
+ && newIndex >= 0
+ && newIndex < currentTimeline.getWindowCount());
+ newIndex = Math.min(newIndex, currentTimeline.getWindowCount() - (toIndex - fromIndex));
+ if (fromIndex == toIndex || fromIndex == newIndex) {
+ // Do nothing.
+ return;
+ }
+ int[] uids = new int[toIndex - fromIndex];
+ for (int i = 0; i < uids.length; i++) {
+ uids[i] = (int) currentTimeline.getWindow(/* windowIndex= */ i + fromIndex, window).uid;
+ }
+ moveMediaItemsInternal(uids, fromIndex, newIndex);
+ }
+
+ @Override
+ public void removeMediaItems(int fromIndex, int toIndex) {
+ Assertions.checkArgument(
+ fromIndex >= 0 && toIndex >= fromIndex && toIndex <= currentTimeline.getWindowCount());
+ if (fromIndex == toIndex) {
+ // Do nothing.
+ return;
+ }
+ int[] uids = new int[toIndex - fromIndex];
+ for (int i = 0; i < uids.length; i++) {
+ uids[i] = (int) currentTimeline.getWindow(/* windowIndex= */ i + fromIndex, window).uid;
+ }
+ removeMediaItemsInternal(uids);
+ }
+
+ @Override
+ public void clearMediaItems() {
+ removeMediaItems(/* fromIndex= */ 0, /* toIndex= */ currentTimeline.getWindowCount());
+ }
+
+ @Override
+ public void prepare() {
+ // Do nothing.
+ }
+
+ @Override
@Player.State
public int getPlaybackState() {
return playbackState;
@@ -337,9 +382,16 @@
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}
+ @Deprecated
@Override
@Nullable
public ExoPlaybackException getPlaybackError() {
+ return getPlayerError();
+ }
+
+ @Override
+ @Nullable
+ public ExoPlaybackException getPlayerError() {
return null;
}
@@ -351,7 +403,8 @@
// We update the local state and send the message to the receiver app, which will cause the
// operation to be perceived as synchronous by the user. When the operation reports a result,
// the local state will be updated to reflect the state reported by the Cast SDK.
- setPlayerStateAndNotifyIfChanged(playWhenReady, playbackState);
+ setPlayerStateAndNotifyIfChanged(
+ playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, playbackState);
flushNotifications();
PendingResult<MediaChannelResult> pendingResult =
playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause();
@@ -398,17 +451,33 @@
flushNotifications();
}
+ /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
// Unsupported by the RemoteMediaClient API. Do nothing.
}
+ /** @deprecated Use {@link #getPlaybackSpeed()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
return PlaybackParameters.DEFAULT;
}
@Override
+ public void setPlaybackSpeed(float playbackSpeed) {
+ // Unsupported by the RemoteMediaClient API. Do nothing.
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ return Player.DEFAULT_PLAYBACK_SPEED;
+ }
+
+ @Override
public void stop(boolean reset) {
playbackState = STATE_IDLE;
if (remoteMediaClient != null) {
@@ -625,8 +694,14 @@
newPlayWhenReadyValue = !remoteMediaClient.isPaused();
playWhenReady.clearPendingResultCallback();
}
+ @PlayWhenReadyChangeReason
+ int playWhenReadyChangeReason =
+ newPlayWhenReadyValue != playWhenReady.value
+ ? PLAY_WHEN_READY_CHANGE_REASON_REMOTE
+ : PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
// We do not mask the playback state, so try setting it regardless of the playWhenReady masking.
- setPlayerStateAndNotifyIfChanged(newPlayWhenReadyValue, fetchPlaybackState(remoteMediaClient));
+ setPlayerStateAndNotifyIfChanged(
+ newPlayWhenReadyValue, playWhenReadyChangeReason, fetchPlaybackState(remoteMediaClient));
}
@RequiresNonNull("remoteMediaClient")
@@ -709,6 +784,58 @@
return false;
}
+ @Nullable
+ private PendingResult<MediaChannelResult> setMediaItemsInternal(
+ MediaQueueItem[] mediaQueueItems,
+ int startWindowIndex,
+ long startPositionMs,
+ @RepeatMode int repeatMode) {
+ if (remoteMediaClient == null || mediaQueueItems.length == 0) {
+ return null;
+ }
+ startPositionMs = startPositionMs == C.TIME_UNSET ? 0 : startPositionMs;
+ if (startWindowIndex == C.INDEX_UNSET) {
+ startWindowIndex = getCurrentWindowIndex();
+ startPositionMs = getCurrentPosition();
+ }
+ return remoteMediaClient.queueLoad(
+ mediaQueueItems,
+ Math.min(startWindowIndex, mediaQueueItems.length - 1),
+ getCastRepeatMode(repeatMode),
+ startPositionMs,
+ /* customData= */ null);
+ }
+
+ @Nullable
+ private PendingResult<MediaChannelResult> addMediaItemsInternal(MediaQueueItem[] items, int uid) {
+ if (remoteMediaClient == null || getMediaStatus() == null) {
+ return null;
+ }
+ return remoteMediaClient.queueInsertItems(items, uid, /* customData= */ null);
+ }
+
+ @Nullable
+ private PendingResult<MediaChannelResult> moveMediaItemsInternal(
+ int[] uids, int fromIndex, int newIndex) {
+ if (remoteMediaClient == null || getMediaStatus() == null) {
+ return null;
+ }
+ int insertBeforeIndex = fromIndex < newIndex ? newIndex + uids.length : newIndex;
+ int insertBeforeItemId = MediaQueueItem.INVALID_ITEM_ID;
+ if (insertBeforeIndex < currentTimeline.getWindowCount()) {
+ insertBeforeItemId = (int) currentTimeline.getWindow(insertBeforeIndex, window).uid;
+ }
+ return remoteMediaClient.queueReorderItems(uids, insertBeforeItemId, /* customData= */ null);
+ }
+
+ @Nullable
+ private PendingResult<MediaChannelResult> removeMediaItemsInternal(int[] uids) {
+ if (remoteMediaClient == null || getMediaStatus() == null) {
+ return null;
+ }
+ return remoteMediaClient.queueRemoveItems(uids, /* customData= */ null);
+ }
+
private void setRepeatModeAndNotifyIfChanged(@Player.RepeatMode int repeatMode) {
if (this.repeatMode.value != repeatMode) {
this.repeatMode.value = repeatMode;
@@ -717,14 +844,27 @@
}
}
+ @SuppressWarnings("deprecation")
private void setPlayerStateAndNotifyIfChanged(
- boolean playWhenReady, @Player.State int playbackState) {
- if (this.playWhenReady.value != playWhenReady || this.playbackState != playbackState) {
- this.playWhenReady.value = playWhenReady;
+ boolean playWhenReady,
+ @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
+ @Player.State int playbackState) {
+ boolean playWhenReadyChanged = this.playWhenReady.value != playWhenReady;
+ boolean playbackStateChanged = this.playbackState != playbackState;
+ if (playWhenReadyChanged || playbackStateChanged) {
this.playbackState = playbackState;
+ this.playWhenReady.value = playWhenReady;
notificationsBatch.add(
new ListenerNotificationTask(
- listener -> listener.onPlayerStateChanged(playWhenReady, playbackState)));
+ listener -> {
+ listener.onPlayerStateChanged(playWhenReady, playbackState);
+ if (playbackStateChanged) {
+ listener.onPlaybackStateChanged(playbackState);
+ }
+ if (playWhenReadyChanged) {
+ listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
+ }
+ }));
}
}
@@ -746,6 +886,7 @@
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
updateInternalStateAndNotifyIfChanged();
} else {
+ updateTimelineAndNotifyIfChanged();
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
@@ -845,6 +986,14 @@
}
}
+ private MediaQueueItem[] toMediaQueueItems(List<MediaItem> mediaItems) {
+ MediaQueueItem[] mediaQueueItems = new MediaQueueItem[mediaItems.size()];
+ for (int i = 0; i < mediaItems.size(); i++) {
+ mediaQueueItems[i] = mediaItemConverter.toMediaQueueItem(mediaItems.get(i));
+ }
+ return mediaQueueItems;
+ }
+
// Internal classes.
private final class StatusListener
diff --git a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java
index 1dc2557..182afb0 100644
--- a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java
+++ b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java
@@ -104,16 +104,11 @@
* @return The equivalent {@link Format}.
*/
public static Format mediaTrackToFormat(MediaTrack mediaTrack) {
- return Format.createContainerFormat(
- mediaTrack.getContentId(),
- /* label= */ null,
- mediaTrack.getContentType(),
- /* sampleMimeType= */ null,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- mediaTrack.getLanguage());
+ return new Format.Builder()
+ .setId(mediaTrack.getContentId())
+ .setContainerMimeType(mediaTrack.getContentType())
+ .setLanguage(mediaTrack.getLanguage())
+ .build();
}
private CastUtils() {}
diff --git a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java
index 098803a..ab02b5e 100644
--- a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java
+++ b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java
@@ -18,7 +18,8 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ext.cast.MediaItem.DrmConfiguration;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaQueueItem;
@@ -43,22 +44,24 @@
@Override
public MediaItem toMediaItem(MediaQueueItem item) {
- return getMediaItem(item.getMedia().getCustomData());
+ // `item` came from `toMediaQueueItem()` so the custom JSON data must be set.
+ return getMediaItem(Assertions.checkNotNull(item.getMedia().getCustomData()));
}
@Override
public MediaQueueItem toMediaQueueItem(MediaItem item) {
- if (item.mimeType == null) {
+ Assertions.checkNotNull(item.playbackProperties);
+ if (item.playbackProperties.mimeType == null) {
throw new IllegalArgumentException("The item must specify its mimeType");
}
MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
- if (item.title != null) {
- metadata.putString(MediaMetadata.KEY_TITLE, item.title);
+ if (item.mediaMetadata.title != null) {
+ metadata.putString(MediaMetadata.KEY_TITLE, item.mediaMetadata.title);
}
MediaInfo mediaInfo =
- new MediaInfo.Builder(item.uri.toString())
+ new MediaInfo.Builder(item.playbackProperties.sourceUri.toString())
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
- .setContentType(item.mimeType)
+ .setContentType(item.playbackProperties.mimeType)
.setMetadata(metadata)
.setCustomData(getCustomData(item))
.build();
@@ -71,16 +74,19 @@
try {
JSONObject mediaItemJson = customData.getJSONObject(KEY_MEDIA_ITEM);
MediaItem.Builder builder = new MediaItem.Builder();
- builder.setUri(Uri.parse(mediaItemJson.getString(KEY_URI)));
+ builder.setSourceUri(Uri.parse(mediaItemJson.getString(KEY_URI)));
if (mediaItemJson.has(KEY_TITLE)) {
- builder.setTitle(mediaItemJson.getString(KEY_TITLE));
+ com.google.android.exoplayer2.MediaMetadata mediaMetadata =
+ new com.google.android.exoplayer2.MediaMetadata.Builder()
+ .setTitle(mediaItemJson.getString(KEY_TITLE))
+ .build();
+ builder.setMediaMetadata(mediaMetadata);
}
if (mediaItemJson.has(KEY_MIME_TYPE)) {
builder.setMimeType(mediaItemJson.getString(KEY_MIME_TYPE));
}
if (mediaItemJson.has(KEY_DRM_CONFIGURATION)) {
- builder.setDrmConfiguration(
- getDrmConfiguration(mediaItemJson.getJSONObject(KEY_DRM_CONFIGURATION)));
+ populateDrmConfiguration(mediaItemJson.getJSONObject(KEY_DRM_CONFIGURATION), builder);
}
return builder.build();
} catch (JSONException e) {
@@ -88,25 +94,26 @@
}
}
- private static DrmConfiguration getDrmConfiguration(JSONObject json) throws JSONException {
- UUID uuid = UUID.fromString(json.getString(KEY_UUID));
- Uri licenseUri = Uri.parse(json.getString(KEY_LICENSE_URI));
+ private static void populateDrmConfiguration(JSONObject json, MediaItem.Builder builder)
+ throws JSONException {
+ builder.setDrmUuid(UUID.fromString(json.getString(KEY_UUID)));
+ builder.setDrmLicenseUri(json.getString(KEY_LICENSE_URI));
JSONObject requestHeadersJson = json.getJSONObject(KEY_REQUEST_HEADERS);
HashMap<String, String> requestHeaders = new HashMap<>();
for (Iterator<String> iterator = requestHeadersJson.keys(); iterator.hasNext(); ) {
String key = iterator.next();
requestHeaders.put(key, requestHeadersJson.getString(key));
}
- return new DrmConfiguration(uuid, licenseUri, requestHeaders);
+ builder.setDrmLicenseRequestHeaders(requestHeaders);
}
// Serialization.
- private static JSONObject getCustomData(MediaItem item) {
+ private static JSONObject getCustomData(MediaItem mediaItem) {
JSONObject json = new JSONObject();
try {
- json.put(KEY_MEDIA_ITEM, getMediaItemJson(item));
- JSONObject playerConfigJson = getPlayerConfigJson(item);
+ json.put(KEY_MEDIA_ITEM, getMediaItemJson(mediaItem));
+ @Nullable JSONObject playerConfigJson = getPlayerConfigJson(mediaItem);
if (playerConfigJson != null) {
json.put(KEY_PLAYER_CONFIG, playerConfigJson);
}
@@ -116,18 +123,21 @@
return json;
}
- private static JSONObject getMediaItemJson(MediaItem item) throws JSONException {
+ private static JSONObject getMediaItemJson(MediaItem mediaItem) throws JSONException {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
JSONObject json = new JSONObject();
- json.put(KEY_URI, item.uri.toString());
- json.put(KEY_TITLE, item.title);
- json.put(KEY_MIME_TYPE, item.mimeType);
- if (item.drmConfiguration != null) {
- json.put(KEY_DRM_CONFIGURATION, getDrmConfigurationJson(item.drmConfiguration));
+ json.put(KEY_TITLE, mediaItem.mediaMetadata.title);
+ json.put(KEY_URI, mediaItem.playbackProperties.sourceUri.toString());
+ json.put(KEY_MIME_TYPE, mediaItem.playbackProperties.mimeType);
+ if (mediaItem.playbackProperties.drmConfiguration != null) {
+ json.put(
+ KEY_DRM_CONFIGURATION,
+ getDrmConfigurationJson(mediaItem.playbackProperties.drmConfiguration));
}
return json;
}
- private static JSONObject getDrmConfigurationJson(DrmConfiguration drmConfiguration)
+ private static JSONObject getDrmConfigurationJson(MediaItem.DrmConfiguration drmConfiguration)
throws JSONException {
JSONObject json = new JSONObject();
json.put(KEY_UUID, drmConfiguration.uuid);
@@ -137,11 +147,12 @@
}
@Nullable
- private static JSONObject getPlayerConfigJson(MediaItem item) throws JSONException {
- DrmConfiguration drmConfiguration = item.drmConfiguration;
- if (drmConfiguration == null) {
+ private static JSONObject getPlayerConfigJson(MediaItem mediaItem) throws JSONException {
+ if (mediaItem.playbackProperties == null
+ || mediaItem.playbackProperties.drmConfiguration == null) {
return null;
}
+ MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
String drmScheme;
if (C.WIDEVINE_UUID.equals(drmConfiguration.uuid)) {
diff --git a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java
deleted file mode 100644
index 7ac0da7..0000000
--- a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.cast;
-
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Util;
-import java.util.Collections;
-import java.util.Map;
-import java.util.UUID;
-
-/** Representation of a media item. */
-public final class MediaItem {
-
- /** A builder for {@link MediaItem} instances. */
- public static final class Builder {
-
- @Nullable private Uri uri;
- @Nullable private String title;
- @Nullable private String mimeType;
- @Nullable private DrmConfiguration drmConfiguration;
-
- /** See {@link MediaItem#uri}. */
- public Builder setUri(String uri) {
- return setUri(Uri.parse(uri));
- }
-
- /** See {@link MediaItem#uri}. */
- public Builder setUri(Uri uri) {
- this.uri = uri;
- return this;
- }
-
- /** See {@link MediaItem#title}. */
- public Builder setTitle(String title) {
- this.title = title;
- return this;
- }
-
- /** See {@link MediaItem#mimeType}. */
- public Builder setMimeType(String mimeType) {
- this.mimeType = mimeType;
- return this;
- }
-
- /** See {@link MediaItem#drmConfiguration}. */
- public Builder setDrmConfiguration(DrmConfiguration drmConfiguration) {
- this.drmConfiguration = drmConfiguration;
- return this;
- }
-
- /** Returns a new {@link MediaItem} instance with the current builder values. */
- public MediaItem build() {
- Assertions.checkNotNull(uri);
- return new MediaItem(uri, title, mimeType, drmConfiguration);
- }
- }
-
- /** DRM configuration for a media item. */
- public static final class DrmConfiguration {
-
- /** The UUID of the protection scheme. */
- public final UUID uuid;
-
- /**
- * Optional license server {@link Uri}. If {@code null} then the license server must be
- * specified by the media.
- */
- @Nullable public final Uri licenseUri;
-
- /** Headers that should be attached to any license requests. */
- public final Map<String, String> requestHeaders;
-
- /**
- * Creates an instance.
- *
- * @param uuid See {@link #uuid}.
- * @param licenseUri See {@link #licenseUri}.
- * @param requestHeaders See {@link #requestHeaders}.
- */
- public DrmConfiguration(
- UUID uuid, @Nullable Uri licenseUri, @Nullable Map<String, String> requestHeaders) {
- this.uuid = uuid;
- this.licenseUri = licenseUri;
- this.requestHeaders =
- requestHeaders == null
- ? Collections.emptyMap()
- : Collections.unmodifiableMap(requestHeaders);
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
-
- DrmConfiguration other = (DrmConfiguration) obj;
- return uuid.equals(other.uuid)
- && Util.areEqual(licenseUri, other.licenseUri)
- && requestHeaders.equals(other.requestHeaders);
- }
-
- @Override
- public int hashCode() {
- int result = uuid.hashCode();
- result = 31 * result + (licenseUri != null ? licenseUri.hashCode() : 0);
- result = 31 * result + requestHeaders.hashCode();
- return result;
- }
- }
-
- /** The media {@link Uri}. */
- public final Uri uri;
-
- /** The title of the item, or {@code null} if unspecified. */
- @Nullable public final String title;
-
- /** The mime type for the media, or {@code null} if unspecified. */
- @Nullable public final String mimeType;
-
- /** Optional {@link DrmConfiguration} for the media. */
- @Nullable public final DrmConfiguration drmConfiguration;
-
- private MediaItem(
- Uri uri,
- @Nullable String title,
- @Nullable String mimeType,
- @Nullable DrmConfiguration drmConfiguration) {
- this.uri = uri;
- this.title = title;
- this.mimeType = mimeType;
- this.drmConfiguration = drmConfiguration;
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- MediaItem other = (MediaItem) obj;
- return uri.equals(other.uri)
- && Util.areEqual(title, other.title)
- && Util.areEqual(mimeType, other.mimeType)
- && Util.areEqual(drmConfiguration, other.drmConfiguration);
- }
-
- @Override
- public int hashCode() {
- int result = uri.hashCode();
- result = 31 * result + (title == null ? 0 : title.hashCode());
- result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode());
- result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
- return result;
- }
-}
diff --git a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemConverter.java b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemConverter.java
index 23633aa..c4a5184 100644
--- a/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemConverter.java
+++ b/tree/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemConverter.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.ext.cast;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.gms.cast.MediaQueueItem;
/** Converts between {@link MediaItem} and the Cast SDK's {@link MediaQueueItem}. */
diff --git a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java
index 1346c1f..79cf9aa 100644
--- a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java
+++ b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastPlayerTest.java
@@ -18,13 +18,22 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
@@ -33,6 +42,9 @@
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,9 +58,13 @@
public class CastPlayerTest {
private CastPlayer castPlayer;
+
+ @SuppressWarnings("deprecation")
private RemoteMediaClient.Listener remoteMediaClientListener;
+
@Mock private RemoteMediaClient mockRemoteMediaClient;
@Mock private MediaStatus mockMediaStatus;
+ @Mock private MediaInfo mockMediaInfo;
@Mock private MediaQueue mockMediaQueue;
@Mock private CastContext mockCastContext;
@Mock private SessionManager mockSessionManager;
@@ -62,6 +78,9 @@
@Captor private ArgumentCaptor<RemoteMediaClient.Listener> listenerArgumentCaptor;
+ @Captor private ArgumentCaptor<MediaQueueItem[]> queueItemsArgumentCaptor;
+
+ @SuppressWarnings("deprecation")
@Before
public void setUp() {
initMocks(this);
@@ -80,8 +99,9 @@
remoteMediaClientListener = listenerArgumentCaptor.getValue();
}
+ @SuppressWarnings("deprecation")
@Test
- public void testSetPlayWhenReady_masksRemoteState() {
+ public void setPlayWhenReady_masksRemoteState() {
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
assertThat(castPlayer.getPlayWhenReady()).isFalse();
@@ -89,6 +109,8 @@
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
+ verify(mockListener)
+ .onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
// There is a status update in the middle, which should be hidden by masking.
remoteMediaClientListener.onStatusUpdated();
@@ -102,8 +124,9 @@
verifyNoMoreInteractions(mockListener);
}
+ @SuppressWarnings("deprecation")
@Test
- public void testSetPlayWhenReadyMasking_updatesUponResultChange() {
+ public void setPlayWhenReadyMasking_updatesUponResultChange() {
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
assertThat(castPlayer.getPlayWhenReady()).isFalse();
@@ -111,26 +134,49 @@
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
+ verify(mockListener)
+ .onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
// Upon result, the remote media client is still paused. The state should reflect that.
setResultCallbackArgumentCaptor
.getValue()
.onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class));
verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE);
+ verify(mockListener).onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isFalse();
}
+ @SuppressWarnings("deprecation")
@Test
- public void testPlayWhenReady_changesOnStatusUpdates() {
+ public void setPlayWhenReady_correctChangeReasonOnPause() {
+ when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
+ when(mockRemoteMediaClient.pause()).thenReturn(mockPendingResult);
+ castPlayer.play();
+ assertThat(castPlayer.getPlayWhenReady()).isTrue();
+ verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
+ verify(mockListener)
+ .onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
+
+ castPlayer.pause();
+ assertThat(castPlayer.getPlayWhenReady()).isFalse();
+ verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE);
+ verify(mockListener)
+ .onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void playWhenReady_changesOnStatusUpdates() {
assertThat(castPlayer.getPlayWhenReady()).isFalse();
when(mockRemoteMediaClient.isPaused()).thenReturn(false);
remoteMediaClientListener.onStatusUpdated();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
+ verify(mockListener).onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isTrue();
}
@Test
- public void testSetRepeatMode_masksRemoteState() {
+ public void setRepeatMode_masksRemoteState() {
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), any())).thenReturn(mockPendingResult);
assertThat(castPlayer.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_OFF);
@@ -153,7 +199,7 @@
}
@Test
- public void testSetRepeatMode_updatesUponResultChange() {
+ public void setRepeatMode_updatesUponResultChange() {
when(mockRemoteMediaClient.queueSetRepeatMode(anyInt(), any())).thenReturn(mockPendingResult);
castPlayer.setRepeatMode(Player.REPEAT_MODE_ONE);
@@ -175,11 +221,288 @@
}
@Test
- public void testRepeatMode_changesOnStatusUpdates() {
+ public void repeatMode_changesOnStatusUpdates() {
assertThat(castPlayer.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_OFF);
when(mockMediaStatus.getQueueRepeatMode()).thenReturn(MediaStatus.REPEAT_MODE_REPEAT_SINGLE);
remoteMediaClientListener.onStatusUpdated();
verify(mockListener).onRepeatModeChanged(Player.REPEAT_MODE_ONE);
assertThat(castPlayer.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ONE);
}
+
+ @Test
+ public void setMediaItems_callsRemoteMediaClient() {
+ List<MediaItem> mediaItems = new ArrayList<>();
+ String sourceUri1 = "http://www.google.com/video1";
+ String sourceUri2 = "http://www.google.com/video2";
+ mediaItems.add(
+ new MediaItem.Builder()
+ .setSourceUri(sourceUri1)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .build());
+ mediaItems.add(
+ new MediaItem.Builder()
+ .setSourceUri(sourceUri2)
+ .setMimeType(MimeTypes.APPLICATION_MP4)
+ .build());
+
+ castPlayer.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 2000L);
+
+ verify(mockRemoteMediaClient)
+ .queueLoad(queueItemsArgumentCaptor.capture(), eq(1), anyInt(), eq(2000L), any());
+ MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
+ assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(sourceUri1);
+ assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(sourceUri2);
+ }
+
+ @Test
+ public void setMediaItems_doNotReset_callsRemoteMediaClient() {
+ MediaItem.Builder builder = new MediaItem.Builder();
+ List<MediaItem> mediaItems = new ArrayList<>();
+ String sourceUri1 = "http://www.google.com/video1";
+ String sourceUri2 = "http://www.google.com/video2";
+ mediaItems.add(builder.setSourceUri(sourceUri1).setMimeType(MimeTypes.APPLICATION_MPD).build());
+ mediaItems.add(builder.setSourceUri(sourceUri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
+ int startWindowIndex = C.INDEX_UNSET;
+ long startPositionMs = 2000L;
+
+ castPlayer.setMediaItems(mediaItems, startWindowIndex, startPositionMs);
+
+ verify(mockRemoteMediaClient)
+ .queueLoad(queueItemsArgumentCaptor.capture(), eq(0), anyInt(), eq(0L), any());
+
+ MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
+ assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(sourceUri1);
+ assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(sourceUri2);
+ }
+
+ @Test
+ public void addMediaItems_callsRemoteMediaClient() {
+ MediaItem.Builder builder = new MediaItem.Builder();
+ List<MediaItem> mediaItems = new ArrayList<>();
+ String sourceUri1 = "http://www.google.com/video1";
+ String sourceUri2 = "http://www.google.com/video2";
+ mediaItems.add(builder.setSourceUri(sourceUri1).setMimeType(MimeTypes.APPLICATION_MPD).build());
+ mediaItems.add(builder.setSourceUri(sourceUri2).setMimeType(MimeTypes.APPLICATION_MP4).build());
+
+ castPlayer.addMediaItems(mediaItems);
+
+ verify(mockRemoteMediaClient)
+ .queueInsertItems(
+ queueItemsArgumentCaptor.capture(), eq(MediaQueueItem.INVALID_ITEM_ID), any());
+
+ MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
+ assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(sourceUri1);
+ assertThat(mediaQueueItems[1].getMedia().getContentId()).isEqualTo(sourceUri2);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ public void addMediaItems_insertAtIndex_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+ String sourceUri = "http://www.google.com/video3";
+ MediaItem anotherMediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(sourceUri)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .build();
+
+ // Add another on position 1
+ int index = 1;
+ castPlayer.addMediaItems(index, Collections.singletonList(anotherMediaItem));
+
+ verify(mockRemoteMediaClient)
+ .queueInsertItems(
+ queueItemsArgumentCaptor.capture(),
+ eq((int) mediaItems.get(index).playbackProperties.tag),
+ any());
+
+ MediaQueueItem[] mediaQueueItems = queueItemsArgumentCaptor.getValue();
+ assertThat(mediaQueueItems[0].getMedia().getContentId()).isEqualTo(sourceUri);
+ }
+
+ @Test
+ public void moveMediaItem_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(new int[] {2}, /* insertBeforeItemId= */ 4, /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItem_toBegin_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 0);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(new int[] {2}, /* insertBeforeItemId= */ 1, /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItem_toEnd_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 4);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(
+ new int[] {2},
+ /* insertBeforeItemId= */ MediaQueueItem.INVALID_ITEM_ID,
+ /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItems_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 3, /* newIndex= */ 1);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(
+ new int[] {1, 2, 3}, /* insertBeforeItemId= */ 5, /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItems_toBeginning_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4, /* newIndex= */ 0);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(
+ new int[] {2, 3, 4}, /* insertBeforeItemId= */ 1, /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItems_toEnd_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 2, /* newIndex= */ 3);
+
+ verify(mockRemoteMediaClient)
+ .queueReorderItems(
+ new int[] {1, 2},
+ /* insertBeforeItemId= */ MediaQueueItem.INVALID_ITEM_ID,
+ /* customData= */ null);
+ }
+
+ @Test
+ public void moveMediaItems_noItems_doesNotCallRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 1, /* newIndex= */ 0);
+
+ verify(mockRemoteMediaClient, never()).queueReorderItems(any(), anyInt(), any());
+ }
+
+ @Test
+ public void moveMediaItems_noMove_doesNotCallRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3, /* newIndex= */ 1);
+
+ verify(mockRemoteMediaClient, never()).queueReorderItems(any(), anyInt(), any());
+ }
+
+ @Test
+ public void removeMediaItems_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 4);
+
+ verify(mockRemoteMediaClient).queueRemoveItems(new int[] {2, 3, 4}, /* customData= */ null);
+ }
+
+ @Test
+ public void clearMediaItems_callsRemoteMediaClient() {
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ castPlayer.clearMediaItems();
+
+ verify(mockRemoteMediaClient)
+ .queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ public void addMediaItems_fillsTimeline() {
+ Timeline.Window window = new Timeline.Window();
+ int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 5);
+ List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
+
+ fillTimeline(mediaItems, mediaQueueItemIds);
+
+ Timeline currentTimeline = castPlayer.getCurrentTimeline();
+ for (int i = 0; i < mediaItems.size(); i++) {
+ assertThat(currentTimeline.getWindow(/* windowIndex= */ i, window).uid)
+ .isEqualTo(mediaItems.get(i).playbackProperties.tag);
+ }
+ }
+
+ private int[] createMediaQueueItemIds(int numberOfIds) {
+ int[] mediaQueueItemIds = new int[numberOfIds];
+ for (int i = 0; i < numberOfIds; i++) {
+ mediaQueueItemIds[i] = i + 1;
+ }
+ return mediaQueueItemIds;
+ }
+
+ private List<MediaItem> createMediaItems(int[] mediaQueueItemIds) {
+ MediaItem.Builder builder = new MediaItem.Builder();
+ List<MediaItem> mediaItems = new ArrayList<>();
+ for (int mediaQueueItemId : mediaQueueItemIds) {
+ MediaItem mediaItem =
+ builder
+ .setSourceUri("http://www.google.com/video" + mediaQueueItemId)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .setTag(mediaQueueItemId)
+ .build();
+ mediaItems.add(mediaItem);
+ }
+ return mediaItems;
+ }
+
+ private void fillTimeline(List<MediaItem> mediaItems, int[] mediaQueueItemIds) {
+ Assertions.checkState(mediaItems.size() == mediaQueueItemIds.length);
+ List<MediaQueueItem> queueItems = new ArrayList<>();
+ DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
+ for (MediaItem mediaItem : mediaItems) {
+ queueItems.add(converter.toMediaQueueItem(mediaItem));
+ }
+
+ // Set up mocks to allow the player to update the timeline.
+ when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds);
+ when(mockMediaStatus.getCurrentItemId()).thenReturn(1);
+ when(mockMediaStatus.getMediaInfo()).thenReturn(mockMediaInfo);
+ when(mockMediaInfo.getStreamType()).thenReturn(MediaInfo.STREAM_TYPE_NONE);
+ when(mockMediaStatus.getQueueItems()).thenReturn(queueItems);
+
+ castPlayer.addMediaItems(mediaItems);
+ // Call listener to update the timeline of the player.
+ remoteMediaClientListener.onQueueStatusUpdated();
+ }
}
diff --git a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java
index 69b25e4..cb852eb 100644
--- a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java
+++ b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java
@@ -39,7 +39,7 @@
/** Tests that duration of the current media info is correctly propagated to the timeline. */
@Test
- public void testGetCastTimelinePersistsDuration() {
+ public void getCastTimelinePersistsDuration() {
CastTimelineTracker tracker = new CastTimelineTracker();
RemoteMediaClient remoteMediaClient =
diff --git a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java
index cf9b9d3..fc1413a 100644
--- a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java
+++ b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverterTest.java
@@ -20,7 +20,9 @@
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ext.cast.MediaItem.DrmConfiguration;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.gms.cast.MediaQueueItem;
import java.util.Collections;
import org.junit.Test;
@@ -33,7 +35,8 @@
@Test
public void serialize_deserialize_minimal() {
MediaItem.Builder builder = new MediaItem.Builder();
- MediaItem item = builder.setUri(Uri.parse("http://example.com")).setMimeType("mime").build();
+ MediaItem item =
+ builder.setSourceUri("http://example.com").setMimeType(MimeTypes.APPLICATION_MPD).build();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
MediaQueueItem queueItem = converter.toMediaQueueItem(item);
@@ -47,14 +50,12 @@
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem item =
builder
- .setUri(Uri.parse("http://example.com"))
- .setTitle("title")
- .setMimeType("mime")
- .setDrmConfiguration(
- new DrmConfiguration(
- C.WIDEVINE_UUID,
- Uri.parse("http://license.com"),
- Collections.singletonMap("key", "value")))
+ .setSourceUri(Uri.parse("http://example.com"))
+ .setMediaMetadata(new MediaMetadata.Builder().build())
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri("http://license.com")
+ .setDrmLicenseRequestHeaders(Collections.singletonMap("key", "value"))
.build();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
diff --git a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java b/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java
deleted file mode 100644
index 7b410a8..0000000
--- a/tree/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.cast;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.Uri;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.util.MimeTypes;
-import java.util.HashMap;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test for {@link MediaItem}. */
-@RunWith(AndroidJUnit4.class)
-public class MediaItemTest {
-
- @Test
- public void buildMediaItem_doesNotChangeState() {
- MediaItem.Builder builder = new MediaItem.Builder();
- MediaItem item1 =
- builder
- .setUri(Uri.parse("http://example.com"))
- .setTitle("title")
- .setMimeType(MimeTypes.AUDIO_MP4)
- .build();
- MediaItem item2 = builder.build();
- assertThat(item1).isEqualTo(item2);
- }
-
- @Test
- public void equals_withEqualDrmSchemes_returnsTrue() {
- MediaItem.Builder builder1 = new MediaItem.Builder();
- MediaItem mediaItem1 =
- builder1
- .setUri(Uri.parse("www.google.com"))
- .setDrmConfiguration(buildDrmConfiguration(1))
- .build();
- MediaItem.Builder builder2 = new MediaItem.Builder();
- MediaItem mediaItem2 =
- builder2
- .setUri(Uri.parse("www.google.com"))
- .setDrmConfiguration(buildDrmConfiguration(1))
- .build();
- assertThat(mediaItem1).isEqualTo(mediaItem2);
- }
-
- @Test
- public void equals_withDifferentDrmRequestHeaders_returnsFalse() {
- MediaItem.Builder builder1 = new MediaItem.Builder();
- MediaItem mediaItem1 =
- builder1
- .setUri(Uri.parse("www.google.com"))
- .setDrmConfiguration(buildDrmConfiguration(1))
- .build();
- MediaItem.Builder builder2 = new MediaItem.Builder();
- MediaItem mediaItem2 =
- builder2
- .setUri(Uri.parse("www.google.com"))
- .setDrmConfiguration(buildDrmConfiguration(2))
- .build();
- assertThat(mediaItem1).isNotEqualTo(mediaItem2);
- }
-
- private static MediaItem.DrmConfiguration buildDrmConfiguration(int seed) {
- HashMap<String, String> requestHeaders = new HashMap<>();
- requestHeaders.put("key1", "value1");
- requestHeaders.put("key2", "value2" + seed);
- return new MediaItem.DrmConfiguration(
- C.WIDEVINE_UUID, Uri.parse("www.uri1.com"), requestHeaders);
- }
-}
diff --git a/tree/extensions/cronet/build.gradle b/tree/extensions/cronet/build.gradle
index d5b7a99..742b163 100644
--- a/tree/extensions/cronet/build.gradle
+++ b/tree/extensions/cronet/build.gradle
@@ -35,6 +35,7 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'library')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
diff --git a/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
index 1903e33..9a2e3e4 100644
--- a/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
+++ b/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
@@ -819,7 +819,9 @@
if (matcher.find()) {
try {
long contentLengthFromRange =
- Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1;
+ Long.parseLong(Assertions.checkNotNull(matcher.group(2)))
+ - Long.parseLong(Assertions.checkNotNull(matcher.group(1)))
+ + 1;
if (contentLength < 0) {
// Some proxy servers strip the Content-Length header. Fall back to the length
// calculated here in this case.
@@ -924,16 +926,12 @@
// For POST redirects that aren't 307 or 308, the redirect is followed but request is
// transformed into a GET.
redirectUrlDataSpec =
- new DataSpec(
- Uri.parse(newLocationUrl),
- DataSpec.HTTP_METHOD_GET,
- /* httpBody= */ null,
- dataSpec.absoluteStreamPosition,
- dataSpec.position,
- dataSpec.length,
- dataSpec.key,
- dataSpec.flags,
- dataSpec.httpRequestHeaders);
+ dataSpec
+ .buildUpon()
+ .setUri(newLocationUrl)
+ .setHttpMethod(DataSpec.HTTP_METHOD_GET)
+ .setHttpBody(null)
+ .build();
} else {
redirectUrlDataSpec = dataSpec.withUri(Uri.parse(newLocationUrl));
}
diff --git a/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java b/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java
index 244ba90..355b4ed 100644
--- a/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java
+++ b/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java
@@ -48,18 +48,18 @@
}
@Test
- public void testGetLength() {
+ public void getLength() {
assertThat(byteArrayUploadDataProvider.getLength()).isEqualTo(TEST_DATA.length);
}
@Test
- public void testReadFullBuffer() throws IOException {
+ public void readFullBuffer() throws IOException {
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
}
@Test
- public void testReadPartialBuffer() throws IOException {
+ public void readPartialBuffer() throws IOException {
byte[] firstHalf = Arrays.copyOf(TEST_DATA, TEST_DATA.length / 2);
byte[] secondHalf = Arrays.copyOfRange(TEST_DATA, TEST_DATA.length / 2, TEST_DATA.length);
byteBuffer = ByteBuffer.allocate(TEST_DATA.length / 2);
@@ -75,7 +75,7 @@
}
@Test
- public void testRewind() throws IOException {
+ public void rewind() throws IOException {
// Read all the data.
byteArrayUploadDataProvider.read(mockUploadDataSink, byteBuffer);
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
diff --git a/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java
index 47f6fa7..bf1ae19 100644
--- a/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java
+++ b/tree/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -63,9 +63,13 @@
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadows.ShadowLooper;
/** Tests for {@link CronetDataSource}. */
@RunWith(AndroidJUnit4.class)
+@LooperMode(Mode.PAUSED)
public final class CronetDataSourceTest {
private static final int TEST_CONNECT_TIMEOUT_MS = 100;
@@ -120,12 +124,15 @@
when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest);
mockStatusResponse();
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL));
testPostDataSpec =
- new DataSpec(Uri.parse(TEST_URL), TEST_POST_BODY, 0, 0, C.LENGTH_UNSET, null, 0);
+ new DataSpec.Builder()
+ .setUri(TEST_URL)
+ .setHttpMethod(DataSpec.HTTP_METHOD_POST)
+ .setHttpBody(TEST_POST_BODY)
+ .build();
testHeadDataSpec =
- new DataSpec(
- Uri.parse(TEST_URL), DataSpec.HTTP_METHOD_HEAD, null, 0, 0, C.LENGTH_UNSET, null, 0);
+ new DataSpec.Builder().setUri(TEST_URL).setHttpMethod(DataSpec.HTTP_METHOD_HEAD).build();
testResponseHeader = new HashMap<>();
testResponseHeader.put("Content-Type", TEST_CONTENT_TYPE);
// This value can be anything since the DataSpec is unset.
@@ -151,7 +158,7 @@
}
@Test
- public void testOpeningTwiceThrows() throws HttpDataSourceException {
+ public void openingTwiceThrows() throws HttpDataSourceException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
try {
@@ -163,7 +170,7 @@
}
@Test
- public void testCallbackFromPreviousRequest() throws HttpDataSourceException {
+ public void callbackFromPreviousRequest() throws HttpDataSourceException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -186,7 +193,7 @@
}
@Test
- public void testRequestStartCalled() throws HttpDataSourceException {
+ public void requestStartCalled() throws HttpDataSourceException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -196,8 +203,8 @@
}
@Test
- public void testRequestSetsRangeHeader() throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ public void requestSetsRangeHeader() throws HttpDataSourceException {
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -206,8 +213,7 @@
}
@Test
- public void testRequestHeadersSet() throws HttpDataSourceException {
-
+ public void requestHeadersSet() throws HttpDataSourceException {
Map<String, String> headersSet = new HashMap<>();
doAnswer(
(invocation) -> {
@@ -227,17 +233,14 @@
dataSpecRequestProperties.put("defaultHeader3", "dataSpecOverridesAll");
dataSpecRequestProperties.put("dataSourceHeader2", "dataSpecOverridesDataSource");
dataSpecRequestProperties.put("dataSpecHeader1", "dataSpecValue1");
+
testDataSpec =
- new DataSpec(
- /* uri= */ Uri.parse(TEST_URL),
- /* httpMethod= */ DataSpec.HTTP_METHOD_GET,
- /* httpBody= */ null,
- /* absoluteStreamPosition= */ 1000,
- /* position= */ 1000,
- /* length= */ 5000,
- /* key= */ null,
- /* flags= */ 0,
- dataSpecRequestProperties);
+ new DataSpec.Builder()
+ .setUri(TEST_URL)
+ .setPosition(1000)
+ .setLength(5000)
+ .setHttpRequestHeaders(dataSpecRequestProperties)
+ .build();
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -253,7 +256,7 @@
}
@Test
- public void testRequestOpen() throws HttpDataSourceException {
+ public void requestOpen() throws HttpDataSourceException {
mockResponseStartSuccess();
assertThat(dataSourceUnderTest.open(testDataSpec)).isEqualTo(TEST_CONTENT_LENGTH);
verify(mockTransferListener)
@@ -261,9 +264,8 @@
}
@Test
- public void testRequestOpenGzippedCompressedReturnsDataSpecLength()
- throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 5000, null);
+ public void requestOpenGzippedCompressedReturnsDataSpecLength() throws HttpDataSourceException {
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 5000);
testResponseHeader.put("Content-Encoding", "gzip");
testResponseHeader.put("Content-Length", Long.toString(50L));
mockResponseStartSuccess();
@@ -274,7 +276,7 @@
}
@Test
- public void testRequestOpenFail() {
+ public void requestOpenFail() {
mockResponseStartFailure();
try {
@@ -292,14 +294,14 @@
@Test
public void open_ifBodyIsSetWithoutContentTypeHeader_fails() {
testDataSpec =
- new DataSpec(
- /* uri= */ Uri.parse(TEST_URL),
- /* postBody= */ new byte[1024],
- /* absoluteStreamPosition= */ 200,
- /* position= */ 200,
- /* length= */ 1024,
- /* key= */ "key",
- /* flags= */ 0);
+ new DataSpec.Builder()
+ .setUri(TEST_URL)
+ .setHttpMethod(DataSpec.HTTP_METHOD_POST)
+ .setHttpBody(new byte[1024])
+ .setPosition(200)
+ .setLength(1024)
+ .setKey("key")
+ .build();
try {
dataSourceUnderTest.open(testDataSpec);
@@ -310,7 +312,7 @@
}
@Test
- public void testRequestOpenFailDueToDnsFailure() {
+ public void requestOpenFailDueToDnsFailure() {
mockResponseStartFailure();
when(mockNetworkException.getErrorCode())
.thenReturn(NetworkException.ERROR_HOSTNAME_NOT_RESOLVED);
@@ -328,7 +330,7 @@
}
@Test
- public void testRequestOpenValidatesStatusCode() {
+ public void requestOpenValidatesStatusCode() {
mockResponseStartSuccess();
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode
@@ -345,7 +347,7 @@
}
@Test
- public void testRequestOpenValidatesContentTypePredicate() {
+ public void requestOpenValidatesContentTypePredicate() {
mockResponseStartSuccess();
ArrayList<String> testedContentTypes = new ArrayList<>();
@@ -368,7 +370,7 @@
}
@Test
- public void testPostRequestOpen() throws HttpDataSourceException {
+ public void postRequestOpen() throws HttpDataSourceException {
mockResponseStartSuccess();
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
@@ -378,7 +380,7 @@
}
@Test
- public void testPostRequestOpenValidatesContentType() {
+ public void postRequestOpenValidatesContentType() {
mockResponseStartSuccess();
try {
@@ -390,7 +392,7 @@
}
@Test
- public void testPostRequestOpenRejects307Redirects() {
+ public void postRequestOpenRejects307Redirects() {
mockResponseStartSuccess();
mockResponseStartRedirect();
@@ -404,7 +406,7 @@
}
@Test
- public void testHeadRequestOpen() throws HttpDataSourceException {
+ public void headRequestOpen() throws HttpDataSourceException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testHeadDataSpec);
verify(mockTransferListener)
@@ -413,7 +415,7 @@
}
@Test
- public void testRequestReadTwice() throws HttpDataSourceException {
+ public void requestReadTwice() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -436,7 +438,7 @@
}
@Test
- public void testSecondRequestNoContentLength() throws HttpDataSourceException {
+ public void secondRequestNoContentLength() throws HttpDataSourceException {
mockResponseStartSuccess();
testResponseHeader.put("Content-Length", Long.toString(1L));
mockReadSuccess(0, 16);
@@ -462,7 +464,7 @@
}
@Test
- public void testReadWithOffset() throws HttpDataSourceException {
+ public void readWithOffset() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -477,11 +479,11 @@
}
@Test
- public void testRangeRequestWith206Response() throws HttpDataSourceException {
+ public void rangeRequestWith206Response() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(1000, 5000);
testUrlResponseInfo = createUrlResponseInfo(206); // Server supports range requests.
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
dataSourceUnderTest.open(testDataSpec);
@@ -494,11 +496,11 @@
}
@Test
- public void testRangeRequestWith200Response() throws HttpDataSourceException {
+ public void rangeRequestWith200Response() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 7000);
testUrlResponseInfo = createUrlResponseInfo(200); // Server does not support range requests.
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
dataSourceUnderTest.open(testDataSpec);
@@ -511,7 +513,7 @@
}
@Test
- public void testReadWithUnsetLength() throws HttpDataSourceException {
+ public void readWithUnsetLength() throws HttpDataSourceException {
testResponseHeader.remove("Content-Length");
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -527,7 +529,7 @@
}
@Test
- public void testReadReturnsWhatItCan() throws HttpDataSourceException {
+ public void readReturnsWhatItCan() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -542,7 +544,7 @@
}
@Test
- public void testClosedMeansClosed() throws HttpDataSourceException {
+ public void closedMeansClosed() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -570,8 +572,8 @@
}
@Test
- public void testOverread() throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16, null);
+ public void overread() throws HttpDataSourceException {
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16);
testResponseHeader.put("Content-Length", Long.toString(16L));
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -623,7 +625,7 @@
}
@Test
- public void testRequestReadByteBufferTwice() throws HttpDataSourceException {
+ public void requestReadByteBufferTwice() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -649,7 +651,7 @@
}
@Test
- public void testRequestIntermixRead() throws HttpDataSourceException {
+ public void requestIntermixRead() throws HttpDataSourceException {
mockResponseStartSuccess();
// Chunking reads into parts 6, 7, 8, 9.
mockReadSuccess(0, 30);
@@ -691,7 +693,7 @@
}
@Test
- public void testSecondRequestNoContentLengthReadByteBuffer() throws HttpDataSourceException {
+ public void secondRequestNoContentLengthReadByteBuffer() throws HttpDataSourceException {
mockResponseStartSuccess();
testResponseHeader.put("Content-Length", Long.toString(1L));
mockReadSuccess(0, 16);
@@ -720,11 +722,11 @@
}
@Test
- public void testRangeRequestWith206ResponseReadByteBuffer() throws HttpDataSourceException {
+ public void rangeRequestWith206ResponseReadByteBuffer() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(1000, 5000);
testUrlResponseInfo = createUrlResponseInfo(206); // Server supports range requests.
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
dataSourceUnderTest.open(testDataSpec);
@@ -738,12 +740,12 @@
}
@Test
- public void testRangeRequestWith200ResponseReadByteBuffer() throws HttpDataSourceException {
+ public void rangeRequestWith200ResponseReadByteBuffer() throws HttpDataSourceException {
// Tests for skipping bytes.
mockResponseStartSuccess();
mockReadSuccess(0, 7000);
testUrlResponseInfo = createUrlResponseInfo(200); // Server does not support range requests.
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
dataSourceUnderTest.open(testDataSpec);
@@ -757,7 +759,7 @@
}
@Test
- public void testReadByteBufferWithUnsetLength() throws HttpDataSourceException {
+ public void readByteBufferWithUnsetLength() throws HttpDataSourceException {
testResponseHeader.remove("Content-Length");
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -775,7 +777,7 @@
}
@Test
- public void testReadByteBufferReturnsWhatItCan() throws HttpDataSourceException {
+ public void readByteBufferReturnsWhatItCan() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -791,8 +793,8 @@
}
@Test
- public void testOverreadByteBuffer() throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16, null);
+ public void overreadByteBuffer() throws HttpDataSourceException {
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16);
testResponseHeader.put("Content-Length", Long.toString(16L));
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -847,7 +849,7 @@
}
@Test
- public void testClosedMeansClosedReadByteBuffer() throws HttpDataSourceException {
+ public void closedMeansClosedReadByteBuffer() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadSuccess(0, 16);
@@ -877,7 +879,7 @@
}
@Test
- public void testConnectTimeout() throws InterruptedException {
+ public void connectTimeout() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
@@ -903,10 +905,12 @@
// We should still be trying to open.
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
+ setSystemClockInMsAndTriggerPendingMessages(
+ /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// Now we timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10);
+ setSystemClockInMsAndTriggerPendingMessages(
+ /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10);
timedOutLatch.await();
verify(mockTransferListener, never())
@@ -914,7 +918,7 @@
}
@Test
- public void testConnectInterrupted() throws InterruptedException {
+ public void connectInterrupted() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
@@ -942,7 +946,8 @@
// We should still be trying to open.
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
+ setSystemClockInMsAndTriggerPendingMessages(
+ /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// Now we interrupt.
thread.interrupt();
@@ -953,7 +958,7 @@
}
@Test
- public void testConnectResponseBeforeTimeout() throws Exception {
+ public void connectResponseBeforeTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch openLatch = new CountDownLatch(1);
@@ -976,7 +981,8 @@
// We should still be trying to open.
assertNotCountedDown(openLatch);
// We should still be trying to open as we approach the timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
+ setSystemClockInMsAndTriggerPendingMessages(
+ /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(openLatch);
// The response arrives just in time.
dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
@@ -985,7 +991,7 @@
}
@Test
- public void testRedirectIncreasesConnectionTimeout() throws Exception {
+ public void redirectIncreasesConnectionTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
@@ -1011,14 +1017,15 @@
// We should still be trying to open.
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
+ setSystemClockInMsAndTriggerPendingMessages(
+ /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1");
long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1;
- SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1);
+ setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs - 1);
// We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
@@ -1026,11 +1033,11 @@
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2");
newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2;
- SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1);
+ setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs - 1);
// We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch);
// Now we timeout.
- SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10);
+ setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs + 10);
timedOutLatch.await();
verify(mockTransferListener, never())
@@ -1039,7 +1046,7 @@
}
@Test
- public void testRedirectParseAndAttachCookie_dataSourceDoesNotHandleSetCookie_followsRedirect()
+ public void redirectParseAndAttachCookie_dataSourceDoesNotHandleSetCookie_followsRedirect()
throws HttpDataSourceException {
mockSingleRedirectSuccess();
mockFollowRedirectSuccess();
@@ -1084,7 +1091,7 @@
public void
testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader()
throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
dataSourceUnderTest =
new CronetDataSource(
mockCronetEngine,
@@ -1111,7 +1118,7 @@
}
@Test
- public void testRedirectNoSetCookieFollowsRedirect() throws HttpDataSourceException {
+ public void redirectNoSetCookieFollowsRedirect() throws HttpDataSourceException {
mockSingleRedirectSuccess();
mockFollowRedirectSuccess();
@@ -1121,7 +1128,7 @@
}
@Test
- public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()
+ public void redirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()
throws HttpDataSourceException {
dataSourceUnderTest =
new CronetDataSource(
@@ -1143,7 +1150,7 @@
}
@Test
- public void testExceptionFromTransferListener() throws HttpDataSourceException {
+ public void exceptionFromTransferListener() throws HttpDataSourceException {
mockResponseStartSuccess();
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
@@ -1163,7 +1170,7 @@
}
@Test
- public void testReadFailure() throws HttpDataSourceException {
+ public void readFailure() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadFailure();
@@ -1178,7 +1185,7 @@
}
@Test
- public void testReadByteBufferFailure() throws HttpDataSourceException {
+ public void readByteBufferFailure() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadFailure();
@@ -1193,7 +1200,7 @@
}
@Test
- public void testReadNonDirectedByteBufferFailure() throws HttpDataSourceException {
+ public void readNonDirectedByteBufferFailure() throws HttpDataSourceException {
mockResponseStartSuccess();
mockReadFailure();
@@ -1208,7 +1215,7 @@
}
@Test
- public void testReadInterrupted() throws HttpDataSourceException, InterruptedException {
+ public void readInterrupted() throws HttpDataSourceException, InterruptedException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -1239,7 +1246,7 @@
}
@Test
- public void testReadByteBufferInterrupted() throws HttpDataSourceException, InterruptedException {
+ public void readByteBufferInterrupted() throws HttpDataSourceException, InterruptedException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -1270,8 +1277,8 @@
}
@Test
- public void testAllowDirectExecutor() throws HttpDataSourceException {
- testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
+ public void allowDirectExecutor() throws HttpDataSourceException {
+ testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000);
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
@@ -1460,4 +1467,9 @@
}
return copy;
}
+
+ private static void setSystemClockInMsAndTriggerPendingMessages(long nowMs) {
+ SystemClock.setCurrentTimeMillis(nowMs);
+ ShadowLooper.idleMainLooper();
+ }
}
diff --git a/tree/extensions/ffmpeg/README.md b/tree/extensions/ffmpeg/README.md
index 1b2db8f..f6e3944 100644
--- a/tree/extensions/ffmpeg/README.md
+++ b/tree/extensions/ffmpeg/README.md
@@ -35,22 +35,22 @@
NDK_PATH="<path to Android NDK>"
```
-* Set up host platform ("darwin-x86_64" for Mac OS X):
+* Set the host platform (use "darwin-x86_64" for Mac OS X):
```
HOST_PLATFORM="linux-x86_64"
```
-* Configure the formats supported by adapting the following variable if needed
- and by setting it. See the [Supported formats][] page for more details of the
- formats.
+* Configure the decoders to include. See the [Supported formats][] page for
+ details of the available decoders, and which formats they support.
```
ENABLED_DECODERS=(vorbis opus flac)
```
-* Fetch and build FFmpeg. For example, executing script `build_ffmpeg.sh` will
- fetch and build FFmpeg release 4.2 for armeabi-v7a, arm64-v8a and x86:
+* Fetch and build FFmpeg. Executing `build_ffmpeg.sh` will fetch and build
+ FFmpeg 4.2 for `armeabi-v7a`, `arm64-v8a`, `x86` and `x86_64`. The script can
+ be edited if you need to build for different architectures.
```
cd "${FFMPEG_EXT_PATH}" && \
@@ -63,7 +63,7 @@
```
cd "${FFMPEG_EXT_PATH}" && \
-${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4
+${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86 x86_64" -j4
```
## Build instructions (Windows) ##
diff --git a/tree/extensions/ffmpeg/build.gradle b/tree/extensions/ffmpeg/build.gradle
index 657fa75..26a72ae 100644
--- a/tree/extensions/ffmpeg/build.gradle
+++ b/tree/extensions/ffmpeg/build.gradle
@@ -40,6 +40,7 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
new file mode 100644
index 0000000..c5072a3
--- /dev/null
+++ b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.ext.ffmpeg;
+
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.SimpleDecoder;
+import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.util.Util;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/** FFmpeg audio decoder. */
+/* package */ final class FfmpegAudioDecoder
+ extends SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> {
+
+ // Output buffer sizes when decoding PCM mu-law streams, which is the maximum FFmpeg outputs.
+ private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536;
+ private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2;
+
+ // LINT.IfChange
+ private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
+ private static final int AUDIO_DECODER_ERROR_OTHER = -2;
+ // LINT.ThenChange(../../../../../../../jni/ffmpeg_jni.cc)
+
+ private final String codecName;
+ @Nullable private final byte[] extraData;
+ private final @C.Encoding int encoding;
+ private final int outputBufferSize;
+
+ private long nativeContext; // May be reassigned on resetting the codec.
+ private boolean hasOutputFormat;
+ private volatile int channelCount;
+ private volatile int sampleRate;
+
+ public FfmpegAudioDecoder(
+ int numInputBuffers,
+ int numOutputBuffers,
+ int initialInputBufferSize,
+ Format format,
+ boolean outputFloat)
+ throws FfmpegDecoderException {
+ super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
+ if (!FfmpegLibrary.isAvailable()) {
+ throw new FfmpegDecoderException("Failed to load decoder native libraries.");
+ }
+ Assertions.checkNotNull(format.sampleMimeType);
+ codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
+ extraData = getExtraData(format.sampleMimeType, format.initializationData);
+ encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
+ outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
+ nativeContext =
+ ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
+ if (nativeContext == 0) {
+ throw new FfmpegDecoderException("Initialization failed.");
+ }
+ setInitialInputBufferSize(initialInputBufferSize);
+ }
+
+ @Override
+ public String getName() {
+ return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName;
+ }
+
+ @Override
+ protected DecoderInputBuffer createInputBuffer() {
+ return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
+ }
+
+ @Override
+ protected SimpleOutputBuffer createOutputBuffer() {
+ return new SimpleOutputBuffer(this::releaseOutputBuffer);
+ }
+
+ @Override
+ protected FfmpegDecoderException createUnexpectedDecodeException(Throwable error) {
+ return new FfmpegDecoderException("Unexpected decode error", error);
+ }
+
+ @Override
+ protected @Nullable FfmpegDecoderException decode(
+ DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) {
+ if (reset) {
+ nativeContext = ffmpegReset(nativeContext, extraData);
+ if (nativeContext == 0) {
+ return new FfmpegDecoderException("Error resetting (see logcat).");
+ }
+ }
+ ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
+ int inputSize = inputData.limit();
+ ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
+ int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize);
+ if (result == AUDIO_DECODER_ERROR_INVALID_DATA) {
+ // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will
+ // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's
+ // position is reset when more audio is produced.
+ outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY);
+ return null;
+ } else if (result == AUDIO_DECODER_ERROR_OTHER) {
+ return new FfmpegDecoderException("Error decoding (see logcat).");
+ }
+ if (!hasOutputFormat) {
+ channelCount = ffmpegGetChannelCount(nativeContext);
+ sampleRate = ffmpegGetSampleRate(nativeContext);
+ if (sampleRate == 0 && "alac".equals(codecName)) {
+ Assertions.checkNotNull(extraData);
+ // ALAC decoder did not set the sample rate in earlier versions of FFmpeg. See
+ // https://trac.ffmpeg.org/ticket/6096.
+ ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
+ parsableExtraData.setPosition(extraData.length - 4);
+ sampleRate = parsableExtraData.readUnsignedIntToInt();
+ }
+ hasOutputFormat = true;
+ }
+ outputData.position(0);
+ outputData.limit(result);
+ return null;
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ ffmpegRelease(nativeContext);
+ nativeContext = 0;
+ }
+
+ /** Returns the channel count of output audio. */
+ public int getChannelCount() {
+ return channelCount;
+ }
+
+ /** Returns the sample rate of output audio. */
+ public int getSampleRate() {
+ return sampleRate;
+ }
+
+ /** Returns the encoding of output audio. */
+ public @C.Encoding int getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
+ * not required.
+ */
+ private static @Nullable byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
+ switch (mimeType) {
+ case MimeTypes.AUDIO_AAC:
+ case MimeTypes.AUDIO_OPUS:
+ return initializationData.get(0);
+ case MimeTypes.AUDIO_ALAC:
+ return getAlacExtraData(initializationData);
+ case MimeTypes.AUDIO_VORBIS:
+ return getVorbisExtraData(initializationData);
+ default:
+ // Other codecs do not require extra data.
+ return null;
+ }
+ }
+
+ private static byte[] getAlacExtraData(List<byte[]> initializationData) {
+ // FFmpeg's ALAC decoder expects an ALAC atom, which contains the ALAC "magic cookie", as extra
+ // data. initializationData[0] contains only the magic cookie, and so we need to package it into
+ // an ALAC atom. See:
+ // https://ffmpeg.org/doxygen/0.6/alac_8c.html
+ // https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt
+ byte[] magicCookie = initializationData.get(0);
+ int alacAtomLength = 12 + magicCookie.length;
+ ByteBuffer alacAtom = ByteBuffer.allocate(alacAtomLength);
+ alacAtom.putInt(alacAtomLength);
+ alacAtom.putInt(0x616c6163); // type=alac
+ alacAtom.putInt(0); // version=0, flags=0
+ alacAtom.put(magicCookie, /* offset= */ 0, magicCookie.length);
+ return alacAtom.array();
+ }
+
+ private static byte[] getVorbisExtraData(List<byte[]> initializationData) {
+ byte[] header0 = initializationData.get(0);
+ byte[] header1 = initializationData.get(1);
+ byte[] extraData = new byte[header0.length + header1.length + 6];
+ extraData[0] = (byte) (header0.length >> 8);
+ extraData[1] = (byte) (header0.length & 0xFF);
+ System.arraycopy(header0, 0, extraData, 2, header0.length);
+ extraData[header0.length + 2] = 0;
+ extraData[header0.length + 3] = 0;
+ extraData[header0.length + 4] = (byte) (header1.length >> 8);
+ extraData[header0.length + 5] = (byte) (header1.length & 0xFF);
+ System.arraycopy(header1, 0, extraData, header0.length + 6, header1.length);
+ return extraData;
+ }
+
+ private native long ffmpegInitialize(
+ String codecName,
+ @Nullable byte[] extraData,
+ boolean outputFloat,
+ int rawSampleRate,
+ int rawChannelCount);
+
+ private native int ffmpegDecode(
+ long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize);
+
+ private native int ffmpegGetChannelCount(long context);
+
+ private native int ffmpegGetSampleRate(long context);
+
+ private native long ffmpegReset(long context, @Nullable byte[] extraData);
+
+ private native void ffmpegRelease(long context);
+}
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java
index 0673f78..4bb64cb 100644
--- a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java
+++ b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java
@@ -18,24 +18,22 @@
import android.os.Handler;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioSink;
+import com.google.android.exoplayer2.audio.DecoderAudioRenderer;
import com.google.android.exoplayer2.audio.DefaultAudioSink;
-import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
-import java.util.Collections;
+import com.google.android.exoplayer2.util.TraceUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-/**
- * Decodes and renders audio using FFmpeg.
- */
-public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
+/** Decodes and renders audio using FFmpeg. */
+public final class FfmpegAudioRenderer extends DecoderAudioRenderer {
+
+ private static final String TAG = "FfmpegAudioRenderer";
/** The number of input and output buffers. */
private static final int NUM_BUFFERS = 16;
@@ -44,13 +42,15 @@
private final boolean enableFloatOutput;
- private @MonotonicNonNull FfmpegDecoder decoder;
+ private @MonotonicNonNull FfmpegAudioDecoder decoder;
public FfmpegAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
}
/**
+ * Creates a new instance.
+ *
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
@@ -68,6 +68,8 @@
}
/**
+ * Creates a new instance.
+ *
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
@@ -85,22 +87,24 @@
super(
eventHandler,
eventListener,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
audioSink);
this.enableFloatOutput = enableFloatOutput;
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@FormatSupport
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
- Assertions.checkNotNull(format.sampleMimeType);
- if (!FfmpegLibrary.isAvailable()) {
+ protected int supportsFormatInternal(Format format) {
+ String mimeType = Assertions.checkNotNull(format.sampleMimeType);
+ if (!FfmpegLibrary.isAvailable() || !MimeTypes.isAudio(mimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
- } else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType) || !isOutputSupported(format)) {
+ } else if (!FfmpegLibrary.supportsFormat(mimeType) || !isOutputSupported(format)) {
return FORMAT_UNSUPPORTED_SUBTYPE;
- } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
+ } else if (format.drmInitData != null && format.exoMediaCryptoType == null) {
return FORMAT_UNSUPPORTED_DRM;
} else {
return FORMAT_HANDLED;
@@ -109,40 +113,32 @@
@Override
@AdaptiveSupport
- public final int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
+ public final int supportsMixedMimeTypeAdaptation() {
return ADAPTIVE_NOT_SEAMLESS;
}
@Override
- protected FfmpegDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
+ protected FfmpegAudioDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws FfmpegDecoderException {
+ TraceUtil.beginSection("createFfmpegAudioDecoder");
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
decoder =
- new FfmpegDecoder(
+ new FfmpegAudioDecoder(
NUM_BUFFERS, NUM_BUFFERS, initialInputBufferSize, format, shouldUseFloatOutput(format));
+ TraceUtil.endSection();
return decoder;
}
@Override
public Format getOutputFormat() {
Assertions.checkNotNull(decoder);
- int channelCount = decoder.getChannelCount();
- int sampleRate = decoder.getSampleRate();
- @C.PcmEncoding int encoding = decoder.getEncoding();
- return Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- sampleRate,
- encoding,
- Collections.emptyList(),
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setChannelCount(decoder.getChannelCount())
+ .setSampleRate(decoder.getSampleRate())
+ .setPcmEncoding(decoder.getEncoding())
+ .build();
}
private boolean isOutputSupported(Format inputFormat) {
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java
deleted file mode 100644
index 6fa3d88..0000000
--- a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.ffmpeg;
-
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.util.ParsableByteArray;
-import com.google.android.exoplayer2.util.Util;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-/**
- * FFmpeg audio decoder.
- */
-/* package */ final class FfmpegDecoder extends
- SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> {
-
- // Output buffer sizes when decoding PCM mu-law streams, which is the maximum FFmpeg outputs.
- private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536;
- private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2;
-
- // Error codes matching ffmpeg_jni.cc.
- private static final int DECODER_ERROR_INVALID_DATA = -1;
- private static final int DECODER_ERROR_OTHER = -2;
-
- private final String codecName;
- @Nullable private final byte[] extraData;
- private final @C.Encoding int encoding;
- private final int outputBufferSize;
-
- private long nativeContext; // May be reassigned on resetting the codec.
- private boolean hasOutputFormat;
- private volatile int channelCount;
- private volatile int sampleRate;
-
- public FfmpegDecoder(
- int numInputBuffers,
- int numOutputBuffers,
- int initialInputBufferSize,
- Format format,
- boolean outputFloat)
- throws FfmpegDecoderException {
- super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
- if (!FfmpegLibrary.isAvailable()) {
- throw new FfmpegDecoderException("Failed to load decoder native libraries.");
- }
- Assertions.checkNotNull(format.sampleMimeType);
- codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
- extraData = getExtraData(format.sampleMimeType, format.initializationData);
- encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
- outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
- nativeContext =
- ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
- if (nativeContext == 0) {
- throw new FfmpegDecoderException("Initialization failed.");
- }
- setInitialInputBufferSize(initialInputBufferSize);
- }
-
- @Override
- public String getName() {
- return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName;
- }
-
- @Override
- protected DecoderInputBuffer createInputBuffer() {
- return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
- }
-
- @Override
- protected SimpleOutputBuffer createOutputBuffer() {
- return new SimpleOutputBuffer(this);
- }
-
- @Override
- protected FfmpegDecoderException createUnexpectedDecodeException(Throwable error) {
- return new FfmpegDecoderException("Unexpected decode error", error);
- }
-
- @Override
- protected @Nullable FfmpegDecoderException decode(
- DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) {
- if (reset) {
- nativeContext = ffmpegReset(nativeContext, extraData);
- if (nativeContext == 0) {
- return new FfmpegDecoderException("Error resetting (see logcat).");
- }
- }
- ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
- int inputSize = inputData.limit();
- ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
- int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize);
- if (result == DECODER_ERROR_INVALID_DATA) {
- // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will
- // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's
- // position is reset when more audio is produced.
- outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY);
- return null;
- } else if (result == DECODER_ERROR_OTHER) {
- return new FfmpegDecoderException("Error decoding (see logcat).");
- }
- if (!hasOutputFormat) {
- channelCount = ffmpegGetChannelCount(nativeContext);
- sampleRate = ffmpegGetSampleRate(nativeContext);
- if (sampleRate == 0 && "alac".equals(codecName)) {
- Assertions.checkNotNull(extraData);
- // ALAC decoder did not set the sample rate in earlier versions of FFMPEG.
- // See https://trac.ffmpeg.org/ticket/6096
- ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
- parsableExtraData.setPosition(extraData.length - 4);
- sampleRate = parsableExtraData.readUnsignedIntToInt();
- }
- hasOutputFormat = true;
- }
- outputData.position(0);
- outputData.limit(result);
- return null;
- }
-
- @Override
- public void release() {
- super.release();
- ffmpegRelease(nativeContext);
- nativeContext = 0;
- }
-
- /** Returns the channel count of output audio. */
- public int getChannelCount() {
- return channelCount;
- }
-
- /** Returns the sample rate of output audio. */
- public int getSampleRate() {
- return sampleRate;
- }
-
- /**
- * Returns the encoding of output audio.
- */
- public @C.Encoding int getEncoding() {
- return encoding;
- }
-
- /**
- * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
- * not required.
- */
- private static @Nullable byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
- switch (mimeType) {
- case MimeTypes.AUDIO_AAC:
- case MimeTypes.AUDIO_OPUS:
- return initializationData.get(0);
- case MimeTypes.AUDIO_ALAC:
- return getAlacExtraData(initializationData);
- case MimeTypes.AUDIO_VORBIS:
- return getVorbisExtraData(initializationData);
- default:
- // Other codecs do not require extra data.
- return null;
- }
- }
-
- private static byte[] getAlacExtraData(List<byte[]> initializationData) {
- // FFmpeg's ALAC decoder expects an ALAC atom, which contains the ALAC "magic cookie", as extra
- // data. initializationData[0] contains only the magic cookie, and so we need to package it into
- // an ALAC atom. See:
- // https://ffmpeg.org/doxygen/0.6/alac_8c.html
- // https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt
- byte[] magicCookie = initializationData.get(0);
- int alacAtomLength = 12 + magicCookie.length;
- ByteBuffer alacAtom = ByteBuffer.allocate(alacAtomLength);
- alacAtom.putInt(alacAtomLength);
- alacAtom.putInt(0x616c6163); // type=alac
- alacAtom.putInt(0); // version=0, flags=0
- alacAtom.put(magicCookie, /* offset= */ 0, magicCookie.length);
- return alacAtom.array();
- }
-
- private static byte[] getVorbisExtraData(List<byte[]> initializationData) {
- byte[] header0 = initializationData.get(0);
- byte[] header1 = initializationData.get(1);
- byte[] extraData = new byte[header0.length + header1.length + 6];
- extraData[0] = (byte) (header0.length >> 8);
- extraData[1] = (byte) (header0.length & 0xFF);
- System.arraycopy(header0, 0, extraData, 2, header0.length);
- extraData[header0.length + 2] = 0;
- extraData[header0.length + 3] = 0;
- extraData[header0.length + 4] = (byte) (header1.length >> 8);
- extraData[header0.length + 5] = (byte) (header1.length & 0xFF);
- System.arraycopy(header1, 0, extraData, header0.length + 6, header1.length);
- return extraData;
- }
-
- private native long ffmpegInitialize(
- String codecName,
- @Nullable byte[] extraData,
- boolean outputFloat,
- int rawSampleRate,
- int rawChannelCount);
-
- private native int ffmpegDecode(long context, ByteBuffer inputData, int inputSize,
- ByteBuffer outputData, int outputSize);
- private native int ffmpegGetChannelCount(long context);
- private native int ffmpegGetSampleRate(long context);
-
- private native long ffmpegReset(long context, @Nullable byte[] extraData);
-
- private native void ffmpegRelease(long context);
-
-}
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoderException.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoderException.java
index d6b5a62..47d5017 100644
--- a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoderException.java
+++ b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoderException.java
@@ -15,12 +15,10 @@
*/
package com.google.android.exoplayer2.ext.ffmpeg;
-import com.google.android.exoplayer2.audio.AudioDecoderException;
+import com.google.android.exoplayer2.decoder.DecoderException;
-/**
- * Thrown when an FFmpeg decoder error occurs.
- */
-public final class FfmpegDecoderException extends AudioDecoderException {
+/** Thrown when an FFmpeg decoder error occurs. */
+public final class FfmpegDecoderException extends DecoderException {
/* package */ FfmpegDecoderException(String message) {
super(message);
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
index 4639851..e3752aa 100644
--- a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
+++ b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
@@ -33,14 +33,14 @@
private static final String TAG = "FfmpegLibrary";
private static final LibraryLoader LOADER =
- new LibraryLoader("avutil", "avresample", "swresample", "avcodec", "ffmpeg");
+ new LibraryLoader("avutil", "swresample", "avcodec", "ffmpeg");
private FfmpegLibrary() {}
/**
* Override the names of the FFmpeg native libraries. If an application wishes to call this
* method, it must do so before calling any other method defined by this class, and before
- * instantiating a {@link FfmpegAudioRenderer} instance.
+ * instantiating a {@link FfmpegAudioRenderer} or {@link FfmpegVideoRenderer} instance.
*
* @param libraries The names of the FFmpeg native libraries.
*/
@@ -118,6 +118,10 @@
return "pcm_mulaw";
case MimeTypes.AUDIO_ALAW:
return "pcm_alaw";
+ case MimeTypes.VIDEO_H264:
+ return "h264";
+ case MimeTypes.VIDEO_H265:
+ return "hevc";
default:
return null;
}
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java
new file mode 100644
index 0000000..de66358
--- /dev/null
+++ b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.ext.ffmpeg;
+
+import android.os.Handler;
+import android.view.Surface;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.decoder.Decoder;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
+import com.google.android.exoplayer2.util.TraceUtil;
+import com.google.android.exoplayer2.video.DecoderVideoRenderer;
+import com.google.android.exoplayer2.video.VideoDecoderInputBuffer;
+import com.google.android.exoplayer2.video.VideoDecoderOutputBuffer;
+import com.google.android.exoplayer2.video.VideoRendererEventListener;
+
+// TODO: Remove the NOTE below.
+/**
+ * <b>NOTE: This class if under development and is not yet functional.</b>
+ *
+ * <p>Decodes and renders video using FFmpeg.
+ */
+public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
+
+ private static final String TAG = "FfmpegAudioRenderer";
+
+ /**
+ * Creates a new instance.
+ *
+ * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
+ * can attempt to seamlessly join an ongoing playback.
+ * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
+ * null if delivery of events is not required.
+ * @param eventListener A listener of events. May be null if delivery of events is not required.
+ * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
+ * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
+ */
+ public FfmpegVideoRenderer(
+ long allowedJoiningTimeMs,
+ @Nullable Handler eventHandler,
+ @Nullable VideoRendererEventListener eventListener,
+ int maxDroppedFramesToNotify) {
+ super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
+ // TODO: Implement.
+ }
+
+ @Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
+ @RendererCapabilities.Capabilities
+ public final int supportsFormat(Format format) {
+ // TODO: Remove this line and uncomment the implementation below.
+ return FORMAT_UNSUPPORTED_TYPE;
+ /*
+ String mimeType = Assertions.checkNotNull(format.sampleMimeType);
+ if (!FfmpegLibrary.isAvailable() || !MimeTypes.isVideo(mimeType)) {
+ return FORMAT_UNSUPPORTED_TYPE;
+ } else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType)) {
+ return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
+ } else if (format.drmInitData != null && format.exoMediaCryptoType == null) {
+ return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM);
+ } else {
+ return RendererCapabilities.create(
+ FORMAT_HANDLED,
+ ADAPTIVE_SEAMLESS,
+ TUNNELING_NOT_SUPPORTED);
+ }
+ */
+ }
+
+ @SuppressWarnings("return.type.incompatible")
+ @Override
+ protected Decoder<VideoDecoderInputBuffer, VideoDecoderOutputBuffer, FfmpegDecoderException>
+ createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
+ throws FfmpegDecoderException {
+ TraceUtil.beginSection("createFfmpegVideoDecoder");
+ // TODO: Implement, remove the SuppressWarnings annotation, and update the return type to use
+ // the concrete type of the decoder (probably FfmepgVideoDecoder).
+ TraceUtil.endSection();
+ return null;
+ }
+
+ @Override
+ protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
+ throws FfmpegDecoderException {
+ // TODO: Implement.
+ }
+
+ @Override
+ protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) {
+ // TODO: Uncomment the implementation below.
+ /*
+ if (decoder != null) {
+ decoder.setOutputMode(outputMode);
+ }
+ */
+ }
+}
diff --git a/tree/extensions/ffmpeg/src/main/jni/Android.mk b/tree/extensions/ffmpeg/src/main/jni/Android.mk
index 22a4edc..bcaf12c 100644
--- a/tree/extensions/ffmpeg/src/main/jni/Android.mk
+++ b/tree/extensions/ffmpeg/src/main/jni/Android.mk
@@ -22,11 +22,6 @@
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := libavresample
-LOCAL_SRC_FILES := ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so
-include $(PREBUILT_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so
include $(PREBUILT_SHARED_LIBRARY)
@@ -40,6 +35,6 @@
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := ffmpeg_jni.cc
LOCAL_C_INCLUDES := ffmpeg
-LOCAL_SHARED_LIBRARIES := libavcodec libavresample libswresample libavutil
+LOCAL_SHARED_LIBRARIES := libavcodec libswresample libavutil
LOCAL_LDLIBS := -Lffmpeg/android-libs/$(TARGET_ARCH_ABI) -llog
include $(BUILD_SHARED_LIBRARY)
diff --git a/tree/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh b/tree/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh
index a76fa0e..833ea18 100755
--- a/tree/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh
+++ b/tree/extensions/ffmpeg/src/main/jni/build_ffmpeg.sh
@@ -32,8 +32,9 @@
--disable-postproc
--disable-avfilter
--disable-symver
- --enable-avresample
+ --disable-avresample
--enable-swresample
+ --extra-ldexeflags=-pie
"
TOOLCHAIN_PREFIX="${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_PLATFORM}/bin"
for decoder in "${ENABLED_DECODERS[@]}"
@@ -53,7 +54,6 @@
--strip="${TOOLCHAIN_PREFIX}/arm-linux-androideabi-strip" \
--extra-cflags="-march=armv7-a -mfloat-abi=softfp" \
--extra-ldflags="-Wl,--fix-cortex-a8" \
- --extra-ldexeflags=-pie \
${COMMON_OPTIONS}
make -j4
make install-libs
@@ -65,7 +65,6 @@
--cross-prefix="${TOOLCHAIN_PREFIX}/aarch64-linux-android21-" \
--nm="${TOOLCHAIN_PREFIX}/aarch64-linux-android-nm" \
--strip="${TOOLCHAIN_PREFIX}/aarch64-linux-android-strip" \
- --extra-ldexeflags=-pie \
${COMMON_OPTIONS}
make -j4
make install-libs
@@ -77,7 +76,18 @@
--cross-prefix="${TOOLCHAIN_PREFIX}/i686-linux-android16-" \
--nm="${TOOLCHAIN_PREFIX}/i686-linux-android-nm" \
--strip="${TOOLCHAIN_PREFIX}/i686-linux-android-strip" \
- --extra-ldexeflags=-pie \
+ --disable-asm \
+ ${COMMON_OPTIONS}
+make -j4
+make install-libs
+make clean
+./configure \
+ --libdir=android-libs/x86_64 \
+ --arch=x86_64 \
+ --cpu=x86_64 \
+ --cross-prefix="${TOOLCHAIN_PREFIX}/x86_64-linux-android21-" \
+ --nm="${TOOLCHAIN_PREFIX}/x86_64-linux-android-nm" \
+ --strip="${TOOLCHAIN_PREFIX}/x86_64-linux-android-strip" \
--disable-asm \
${COMMON_OPTIONS}
make -j4
diff --git a/tree/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc b/tree/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc
index dcd4560..adbf515 100644
--- a/tree/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc
+++ b/tree/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc
@@ -26,35 +26,35 @@
#include <stdint.h>
#endif
#include <libavcodec/avcodec.h>
-#include <libavresample/avresample.h>
#include <libavutil/channel_layout.h>
#include <libavutil/error.h>
#include <libavutil/opt.h>
+#include <libswresample/swresample.h>
}
#define LOG_TAG "ffmpeg_jni"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
__VA_ARGS__))
-#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
- extern "C" { \
- JNIEXPORT RETURN_TYPE \
- Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegDecoder_ ## NAME \
- (JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
- } \
- JNIEXPORT RETURN_TYPE \
- Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegDecoder_ ## NAME \
- (JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
+#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
+ extern "C" { \
+ JNIEXPORT RETURN_TYPE \
+ Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_##NAME( \
+ JNIEnv *env, jobject thiz, ##__VA_ARGS__); \
+ } \
+ JNIEXPORT RETURN_TYPE \
+ Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_##NAME( \
+ JNIEnv *env, jobject thiz, ##__VA_ARGS__)
-#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
- extern "C" { \
- JNIEXPORT RETURN_TYPE \
- Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_ ## NAME \
- (JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
- } \
- JNIEXPORT RETURN_TYPE \
- Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_ ## NAME \
- (JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
+#define AUDIO_DECODER_FUNC(RETURN_TYPE, NAME, ...) \
+ extern "C" { \
+ JNIEXPORT RETURN_TYPE \
+ Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegAudioDecoder_##NAME( \
+ JNIEnv *env, jobject thiz, ##__VA_ARGS__); \
+ } \
+ JNIEXPORT RETURN_TYPE \
+ Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegAudioDecoder_##NAME( \
+ JNIEnv *env, jobject thiz, ##__VA_ARGS__)
#define ERROR_STRING_BUFFER_LENGTH 256
@@ -63,9 +63,10 @@
// Output format corresponding to AudioFormat.ENCODING_PCM_FLOAT.
static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT;
-// Error codes matching FfmpegDecoder.java.
-static const int DECODER_ERROR_INVALID_DATA = -1;
-static const int DECODER_ERROR_OTHER = -2;
+// LINT.IfChange
+static const int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
+static const int AUDIO_DECODER_ERROR_OTHER = -2;
+// LINT.ThenChange(../java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java)
/**
* Returns the AVCodec with the specified name, or NULL if it is not available.
@@ -83,7 +84,8 @@
/**
* Decodes the packet into the output buffer, returning the number of bytes
- * written, or a negative DECODER_ERROR constant value in the case of an error.
+ * written, or a negative AUDIO_DECODER_ERROR constant value in the case of an
+ * error.
*/
int decodePacket(AVCodecContext *context, AVPacket *packet,
uint8_t *outputBuffer, int outputSize);
@@ -115,8 +117,9 @@
return getCodecByName(env, codecName) != NULL;
}
-DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, jbyteArray extraData,
- jboolean outputFloat, jint rawSampleRate, jint rawChannelCount) {
+AUDIO_DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName,
+ jbyteArray extraData, jboolean outputFloat,
+ jint rawSampleRate, jint rawChannelCount) {
AVCodec *codec = getCodecByName(env, codecName);
if (!codec) {
LOGE("Codec not found.");
@@ -126,8 +129,8 @@
rawChannelCount);
}
-DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
- jint inputSize, jobject outputData, jint outputSize) {
+AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
+ jint inputSize, jobject outputData, jint outputSize) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@@ -154,7 +157,7 @@
outputSize);
}
-DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) {
+AUDIO_DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@@ -162,7 +165,7 @@
return ((AVCodecContext *) context)->channels;
}
-DECODER_FUNC(jint, ffmpegGetSampleRate, jlong context) {
+AUDIO_DECODER_FUNC(jint, ffmpegGetSampleRate, jlong context) {
if (!context) {
LOGE("Context must be non-NULL.");
return -1;
@@ -170,7 +173,7 @@
return ((AVCodecContext *) context)->sample_rate;
}
-DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) {
+AUDIO_DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) {
AVCodecContext *context = (AVCodecContext *) jContext;
if (!context) {
LOGE("Tried to reset without a context.");
@@ -198,7 +201,7 @@
return (jlong) context;
}
-DECODER_FUNC(void, ffmpegRelease, jlong context) {
+AUDIO_DECODER_FUNC(void, ffmpegRelease, jlong context) {
if (context) {
releaseContext((AVCodecContext *) context);
}
@@ -259,8 +262,8 @@
result = avcodec_send_packet(context, packet);
if (result) {
logError("avcodec_send_packet", result);
- return result == AVERROR_INVALIDDATA ? DECODER_ERROR_INVALID_DATA
- : DECODER_ERROR_OTHER;
+ return result == AVERROR_INVALIDDATA ? AUDIO_DECODER_ERROR_INVALID_DATA
+ : AUDIO_DECODER_ERROR_OTHER;
}
// Dequeue output data until it runs out.
@@ -289,11 +292,11 @@
int sampleCount = frame->nb_samples;
int dataSize = av_samples_get_buffer_size(NULL, channelCount, sampleCount,
sampleFormat, 1);
- AVAudioResampleContext *resampleContext;
+ SwrContext *resampleContext;
if (context->opaque) {
- resampleContext = (AVAudioResampleContext *) context->opaque;
+ resampleContext = (SwrContext *)context->opaque;
} else {
- resampleContext = avresample_alloc_context();
+ resampleContext = swr_alloc();
av_opt_set_int(resampleContext, "in_channel_layout", channelLayout, 0);
av_opt_set_int(resampleContext, "out_channel_layout", channelLayout, 0);
av_opt_set_int(resampleContext, "in_sample_rate", sampleRate, 0);
@@ -302,9 +305,9 @@
// The output format is always the requested format.
av_opt_set_int(resampleContext, "out_sample_fmt",
context->request_sample_fmt, 0);
- result = avresample_open(resampleContext);
+ result = swr_init(resampleContext);
if (result < 0) {
- logError("avresample_open", result);
+ logError("swr_init", result);
av_frame_free(&frame);
return -1;
}
@@ -312,7 +315,7 @@
}
int inSampleSize = av_get_bytes_per_sample(sampleFormat);
int outSampleSize = av_get_bytes_per_sample(context->request_sample_fmt);
- int outSamples = avresample_get_out_samples(resampleContext, sampleCount);
+ int outSamples = swr_get_out_samples(resampleContext, sampleCount);
int bufferOutSize = outSampleSize * channelCount * outSamples;
if (outSize + bufferOutSize > outputSize) {
LOGE("Output buffer size (%d) too small for output data (%d).",
@@ -320,15 +323,14 @@
av_frame_free(&frame);
return -1;
}
- result = avresample_convert(resampleContext, &outputBuffer, bufferOutSize,
- outSamples, frame->data, frame->linesize[0],
- sampleCount);
+ result = swr_convert(resampleContext, &outputBuffer, bufferOutSize,
+ (const uint8_t **)frame->data, frame->nb_samples);
av_frame_free(&frame);
if (result < 0) {
- logError("avresample_convert", result);
+ logError("swr_convert", result);
return result;
}
- int available = avresample_available(resampleContext);
+ int available = swr_get_out_samples(resampleContext, 0);
if (available != 0) {
LOGE("Expected no samples remaining after resampling, but found %d.",
available);
@@ -351,9 +353,9 @@
if (!context) {
return;
}
- AVAudioResampleContext *resampleContext;
- if ((resampleContext = (AVAudioResampleContext *) context->opaque)) {
- avresample_free(&resampleContext);
+ SwrContext *swrContext;
+ if ((swrContext = (SwrContext *)context->opaque)) {
+ swr_free(&swrContext);
context->opaque = NULL;
}
avcodec_free_context(&context);
diff --git a/tree/extensions/flac/build.gradle b/tree/extensions/flac/build.gradle
index 4a326ac..f220d21 100644
--- a/tree/extensions/flac/build.gradle
+++ b/tree/extensions/flac/build.gradle
@@ -29,9 +29,12 @@
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
- sourceSets.main {
- jniLibs.srcDir 'src/main/libs'
- jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ sourceSets {
+ main {
+ jniLibs.srcDir 'src/main/libs'
+ jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ }
+ androidTest.assets.srcDir '../../testdata/src/test/assets/'
}
testOptions.unitTests.includeAndroidResources = true
@@ -41,11 +44,13 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation project(modulePrefix + 'testutils')
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
testImplementation 'androidx.test:core:' + androidxTestCoreVersion
testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
testImplementation project(modulePrefix + 'testutils')
+ testImplementation project(modulePrefix + 'testdata')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/extensions/flac/src/androidTest/assets/bear.flac b/tree/extensions/flac/src/androidTest/assets/bear.flac
deleted file mode 100644
index 3e17983..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear.flac
+++ /dev/null
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear.flac.0.dump b/tree/extensions/flac/src/androidTest/assets/bear.flac.0.dump
deleted file mode 100644
index d562052..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear.flac.0.dump
+++ /dev/null
@@ -1,163 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 526272
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 16384, hash 61D2C5C2
- sample 1:
- time = 85333
- flags = 1
- data = length 16384, hash E6D7F214
- sample 2:
- time = 170666
- flags = 1
- data = length 16384, hash 59BF0D5D
- sample 3:
- time = 256000
- flags = 1
- data = length 16384, hash 3625F468
- sample 4:
- time = 341333
- flags = 1
- data = length 16384, hash F66A323
- sample 5:
- time = 426666
- flags = 1
- data = length 16384, hash CDBAE629
- sample 6:
- time = 512000
- flags = 1
- data = length 16384, hash 536F3A91
- sample 7:
- time = 597333
- flags = 1
- data = length 16384, hash D4F35C9C
- sample 8:
- time = 682666
- flags = 1
- data = length 16384, hash EE04CEBF
- sample 9:
- time = 768000
- flags = 1
- data = length 16384, hash 647E2A67
- sample 10:
- time = 853333
- flags = 1
- data = length 16384, hash 31583F2C
- sample 11:
- time = 938666
- flags = 1
- data = length 16384, hash E433A93D
- sample 12:
- time = 1024000
- flags = 1
- data = length 16384, hash 5E1C7051
- sample 13:
- time = 1109333
- flags = 1
- data = length 16384, hash 43E6E358
- sample 14:
- time = 1194666
- flags = 1
- data = length 16384, hash 5DC1B256
- sample 15:
- time = 1280000
- flags = 1
- data = length 16384, hash 3D9D95CF
- sample 16:
- time = 1365333
- flags = 1
- data = length 16384, hash 2A5BD2C0
- sample 17:
- time = 1450666
- flags = 1
- data = length 16384, hash 93E25061
- sample 18:
- time = 1536000
- flags = 1
- data = length 16384, hash B81793D8
- sample 19:
- time = 1621333
- flags = 1
- data = length 16384, hash 1A3BD49F
- sample 20:
- time = 1706666
- flags = 1
- data = length 16384, hash FB672FF1
- sample 21:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 22:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 23:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 24:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 25:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 26:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 27:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 28:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 29:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 30:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 31:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 32:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear.flac.1.dump b/tree/extensions/flac/src/androidTest/assets/bear.flac.1.dump
deleted file mode 100644
index 93f3822..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear.flac.1.dump
+++ /dev/null
@@ -1,123 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 362432
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 16384, hash 31583F2C
- sample 1:
- time = 938666
- flags = 1
- data = length 16384, hash E433A93D
- sample 2:
- time = 1024000
- flags = 1
- data = length 16384, hash 5E1C7051
- sample 3:
- time = 1109333
- flags = 1
- data = length 16384, hash 43E6E358
- sample 4:
- time = 1194666
- flags = 1
- data = length 16384, hash 5DC1B256
- sample 5:
- time = 1280000
- flags = 1
- data = length 16384, hash 3D9D95CF
- sample 6:
- time = 1365333
- flags = 1
- data = length 16384, hash 2A5BD2C0
- sample 7:
- time = 1450666
- flags = 1
- data = length 16384, hash 93E25061
- sample 8:
- time = 1536000
- flags = 1
- data = length 16384, hash B81793D8
- sample 9:
- time = 1621333
- flags = 1
- data = length 16384, hash 1A3BD49F
- sample 10:
- time = 1706666
- flags = 1
- data = length 16384, hash FB672FF1
- sample 11:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 12:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 13:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 14:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 15:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 16:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 17:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 18:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 19:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 20:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 21:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 22:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear.flac.2.dump b/tree/extensions/flac/src/androidTest/assets/bear.flac.2.dump
deleted file mode 100644
index 9c53a95..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear.flac.2.dump
+++ /dev/null
@@ -1,79 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 182208
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 1:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 2:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 3:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 4:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 5:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 6:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 7:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 8:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 9:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 10:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 11:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear.flac.3.dump b/tree/extensions/flac/src/androidTest/assets/bear.flac.3.dump
deleted file mode 100644
index 82e23a2..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear.flac.3.dump
+++ /dev/null
@@ -1,39 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 18368
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 1:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_no_seek.flac b/tree/extensions/flac/src/androidTest/assets/bear_no_seek.flac
deleted file mode 100644
index cd32711..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_no_seek.flac
+++ /dev/null
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac b/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac
deleted file mode 100644
index fc945f1..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac
+++ /dev/null
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.0.dump b/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.0.dump
deleted file mode 100644
index 59a9f37..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.0.dump
+++ /dev/null
@@ -1,163 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- total output bytes = 526272
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 16384, hash 61D2C5C2
- sample 1:
- time = 85333
- flags = 1
- data = length 16384, hash E6D7F214
- sample 2:
- time = 170666
- flags = 1
- data = length 16384, hash 59BF0D5D
- sample 3:
- time = 256000
- flags = 1
- data = length 16384, hash 3625F468
- sample 4:
- time = 341333
- flags = 1
- data = length 16384, hash F66A323
- sample 5:
- time = 426666
- flags = 1
- data = length 16384, hash CDBAE629
- sample 6:
- time = 512000
- flags = 1
- data = length 16384, hash 536F3A91
- sample 7:
- time = 597333
- flags = 1
- data = length 16384, hash D4F35C9C
- sample 8:
- time = 682666
- flags = 1
- data = length 16384, hash EE04CEBF
- sample 9:
- time = 768000
- flags = 1
- data = length 16384, hash 647E2A67
- sample 10:
- time = 853333
- flags = 1
- data = length 16384, hash 31583F2C
- sample 11:
- time = 938666
- flags = 1
- data = length 16384, hash E433A93D
- sample 12:
- time = 1024000
- flags = 1
- data = length 16384, hash 5E1C7051
- sample 13:
- time = 1109333
- flags = 1
- data = length 16384, hash 43E6E358
- sample 14:
- time = 1194666
- flags = 1
- data = length 16384, hash 5DC1B256
- sample 15:
- time = 1280000
- flags = 1
- data = length 16384, hash 3D9D95CF
- sample 16:
- time = 1365333
- flags = 1
- data = length 16384, hash 2A5BD2C0
- sample 17:
- time = 1450666
- flags = 1
- data = length 16384, hash 93E25061
- sample 18:
- time = 1536000
- flags = 1
- data = length 16384, hash B81793D8
- sample 19:
- time = 1621333
- flags = 1
- data = length 16384, hash 1A3BD49F
- sample 20:
- time = 1706666
- flags = 1
- data = length 16384, hash FB672FF1
- sample 21:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 22:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 23:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 24:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 25:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 26:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 27:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 28:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 29:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 30:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 31:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 32:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.1.dump b/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.1.dump
deleted file mode 100644
index a2ad67c..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.1.dump
+++ /dev/null
@@ -1,123 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- total output bytes = 362432
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 16384, hash 31583F2C
- sample 1:
- time = 938666
- flags = 1
- data = length 16384, hash E433A93D
- sample 2:
- time = 1024000
- flags = 1
- data = length 16384, hash 5E1C7051
- sample 3:
- time = 1109333
- flags = 1
- data = length 16384, hash 43E6E358
- sample 4:
- time = 1194666
- flags = 1
- data = length 16384, hash 5DC1B256
- sample 5:
- time = 1280000
- flags = 1
- data = length 16384, hash 3D9D95CF
- sample 6:
- time = 1365333
- flags = 1
- data = length 16384, hash 2A5BD2C0
- sample 7:
- time = 1450666
- flags = 1
- data = length 16384, hash 93E25061
- sample 8:
- time = 1536000
- flags = 1
- data = length 16384, hash B81793D8
- sample 9:
- time = 1621333
- flags = 1
- data = length 16384, hash 1A3BD49F
- sample 10:
- time = 1706666
- flags = 1
- data = length 16384, hash FB672FF1
- sample 11:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 12:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 13:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 14:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 15:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 16:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 17:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 18:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 19:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 20:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 21:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 22:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.2.dump b/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.2.dump
deleted file mode 100644
index 067d67f..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.2.dump
+++ /dev/null
@@ -1,79 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- total output bytes = 182208
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 16384, hash 48AB8B45
- sample 1:
- time = 1877333
- flags = 1
- data = length 16384, hash 13C9640A
- sample 2:
- time = 1962666
- flags = 1
- data = length 16384, hash 499E4A0B
- sample 3:
- time = 2048000
- flags = 1
- data = length 16384, hash F9A783E6
- sample 4:
- time = 2133333
- flags = 1
- data = length 16384, hash D2B77598
- sample 5:
- time = 2218666
- flags = 1
- data = length 16384, hash CE5B826C
- sample 6:
- time = 2304000
- flags = 1
- data = length 16384, hash E99EE956
- sample 7:
- time = 2389333
- flags = 1
- data = length 16384, hash F2DB1486
- sample 8:
- time = 2474666
- flags = 1
- data = length 16384, hash 1636EAB
- sample 9:
- time = 2560000
- flags = 1
- data = length 16384, hash 23457C08
- sample 10:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 11:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.3.dump b/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.3.dump
deleted file mode 100644
index 6edec00..0000000
--- a/tree/extensions/flac/src/androidTest/assets/bear_with_id3.flac.3.dump
+++ /dev/null
@@ -1,39 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 16384
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- total output bytes = 18368
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 16384, hash 30EB8381
- sample 1:
- time = 2730666
- flags = 1
- data = length 1984, hash 59CFDE1B
-tracksEnded = true
diff --git a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeekerTest.java b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeekerTest.java
deleted file mode 100644
index a18202f..0000000
--- a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeekerTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.flac;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.ext.flac.FlacBinarySearchSeeker.OutputFrameHolder;
-import com.google.android.exoplayer2.extractor.SeekMap;
-import com.google.android.exoplayer2.testutil.FakeExtractorInput;
-import com.google.android.exoplayer2.testutil.TestUtil;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit test for {@link FlacBinarySearchSeeker}. */
-@RunWith(AndroidJUnit4.class)
-public final class FlacBinarySearchSeekerTest {
-
- private static final String NOSEEKTABLE_FLAC = "bear_no_seek.flac";
- private static final int DURATION_US = 2_741_000;
-
- @Before
- public void setUp() {
- if (!FlacLibrary.isAvailable()) {
- fail("Flac library not available.");
- }
- }
-
- @Test
- public void testGetSeekMap_returnsSeekMapWithCorrectDuration()
- throws IOException, FlacDecoderException, InterruptedException {
- byte[] data =
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
- FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
- FlacDecoderJni decoderJni = new FlacDecoderJni();
- decoderJni.setData(input);
- OutputFrameHolder outputFrameHolder = new OutputFrameHolder(ByteBuffer.allocateDirect(0));
-
- FlacBinarySearchSeeker seeker =
- new FlacBinarySearchSeeker(
- decoderJni.decodeStreamMetadata(),
- /* firstFramePosition= */ 0,
- data.length,
- decoderJni,
- outputFrameHolder);
- SeekMap seekMap = seeker.getSeekMap();
-
- assertThat(seekMap).isNotNull();
- assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US);
- assertThat(seekMap.isSeekable()).isTrue();
- }
-
- @Test
- public void testSetSeekTargetUs_returnsSeekPending()
- throws IOException, FlacDecoderException, InterruptedException {
- byte[] data =
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
- FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
- FlacDecoderJni decoderJni = new FlacDecoderJni();
- decoderJni.setData(input);
- OutputFrameHolder outputFrameHolder = new OutputFrameHolder(ByteBuffer.allocateDirect(0));
-
- FlacBinarySearchSeeker seeker =
- new FlacBinarySearchSeeker(
- decoderJni.decodeStreamMetadata(),
- /* firstFramePosition= */ 0,
- data.length,
- decoderJni,
- outputFrameHolder);
- seeker.setSeekTargetUs(/* timeUs= */ 1000);
-
- assertThat(seeker.isSeeking()).isTrue();
- }
-}
diff --git a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorSeekTest.java b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorSeekTest.java
index a64a52b..1c0c450 100644
--- a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorSeekTest.java
+++ b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorSeekTest.java
@@ -16,73 +16,43 @@
package com.google.android.exoplayer2.ext.flac;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-import android.content.Context;
import android.net.Uri;
-import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
-import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
-import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
-import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
-import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.List;
-import java.util.Random;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Seeking tests for {@link FlacExtractor} when the FLAC stream does not have a SEEKTABLE. */
+/** Seeking tests for {@link FlacExtractor}. */
@RunWith(AndroidJUnit4.class)
public final class FlacExtractorSeekTest {
- private static final String NO_SEEKTABLE_FLAC = "bear_no_seek.flac";
+ private static final String TEST_FILE_SEEK_TABLE = "flac/bear.flac";
+ private static final String TEST_FILE_BINARY_SEARCH = "flac/bear_one_metadata_block.flac";
+ private static final String TEST_FILE_UNSEEKABLE = "flac/bear_no_seek_table_no_num_samples.flac";
private static final int DURATION_US = 2_741_000;
- private static final Uri FILE_URI = Uri.parse("file:///android_asset/" + NO_SEEKTABLE_FLAC);
- private static final Random RANDOM = new Random(1234L);
- private FakeExtractorOutput expectedOutput;
- private FakeTrackOutput expectedTrackOutput;
-
- private DefaultDataSource dataSource;
- private PositionHolder positionHolder;
- private long totalInputLength;
-
- @Before
- public void setUp() throws Exception {
- if (!FlacLibrary.isAvailable()) {
- fail("Flac library not available.");
- }
- expectedOutput = new FakeExtractorOutput();
- extractAllSamplesFromFileToExpectedOutput(
- ApplicationProvider.getApplicationContext(), NO_SEEKTABLE_FLAC);
- expectedTrackOutput = expectedOutput.trackOutputs.get(0);
-
- dataSource =
- new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
- .createDataSource();
- totalInputLength = readInputLength();
- positionHolder = new PositionHolder();
- }
+ private FlacExtractor extractor = new FlacExtractor();
+ private FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
+ private DefaultDataSource dataSource =
+ new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
+ .createDataSource();
@Test
- public void testFlacExtractorReads_nonSeekTableFile_returnSeekableSeekMap()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
+ public void flacExtractorReads_seekTable_returnSeekableSeekMap() throws IOException {
+ Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_SEEK_TABLE);
- SeekMap seekMap = extractSeekMap(extractor, new FakeExtractorOutput());
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
assertThat(seekMap).isNotNull();
assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US);
@@ -90,205 +60,227 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
-
- FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
- SeekMap seekMap = extractSeekMap(extractor, extractorOutput);
+ public void seeking_seekTable_handlesSeekToZero() throws IOException {
+ String fileName = TEST_FILE_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
- long targetSeekTimeUs = 987_000;
- int extractedFrameIndex = seekToTimeUs(extractor, seekMap, targetSeekTimeUs, trackOutput);
+ long targetSeekTimeUs = 0;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
- assertThat(extractedFrameIndex).isNotEqualTo(-1);
- assertFirstFrameAfterSeekContainTargetSeekTime(
- trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekPrecedesTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Test
- public void testHandlePendingSeek_handlesSeekToEoF_extractsLastFrame()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
-
- FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
- SeekMap seekMap = extractSeekMap(extractor, extractorOutput);
+ public void seeking_seekTable_handlesSeekToEoF() throws IOException {
+ String fileName = TEST_FILE_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
long targetSeekTimeUs = seekMap.getDurationUs();
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
- int extractedFrameIndex = seekToTimeUs(extractor, seekMap, targetSeekTimeUs, trackOutput);
-
- assertThat(extractedFrameIndex).isNotEqualTo(-1);
- assertFirstFrameAfterSeekContainTargetSeekTime(
- trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekPrecedesTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Test
- public void testHandlePendingSeek_handlesSeekingBackward_extractsCorrectFrame()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
+ public void seeking_seekTable_handlesSeekingBackward() throws IOException {
+ String fileName = TEST_FILE_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
- FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
- SeekMap seekMap = extractSeekMap(extractor, extractorOutput);
+ long firstSeekTimeUs = 1_234_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 987_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekPrecedesTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_seekTable_handlesSeekingForward() throws IOException {
+ String fileName = TEST_FILE_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
long firstSeekTimeUs = 987_000;
- seekToTimeUs(extractor, seekMap, firstSeekTimeUs, trackOutput);
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 1_234_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekPrecedesTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void flacExtractorReads_binarySearch_returnSeekableSeekMap() throws IOException {
+ Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_BINARY_SEARCH);
+
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+
+ assertThat(seekMap).isNotNull();
+ assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US);
+ assertThat(seekMap.isSeekable()).isTrue();
+ }
+
+ @Test
+ public void seeking_binarySearch_handlesSeekToZero() throws IOException {
+ String fileName = TEST_FILE_BINARY_SEARCH;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
long targetSeekTimeUs = 0;
- int extractedFrameIndex = seekToTimeUs(extractor, seekMap, targetSeekTimeUs, trackOutput);
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
- assertThat(extractedFrameIndex).isNotEqualTo(-1);
- assertFirstFrameAfterSeekContainTargetSeekTime(
- trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekContainsTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Test
- public void testHandlePendingSeek_handlesSeekingForward_extractsCorrectFrame()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
+ public void seeking_binarySearch_handlesSeekToEoF() throws IOException {
+ String fileName = TEST_FILE_BINARY_SEARCH;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
- FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
- SeekMap seekMap = extractSeekMap(extractor, extractorOutput);
+ long targetSeekTimeUs = seekMap.getDurationUs();
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekContainsTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_binarySearch_handlesSeekingBackward() throws IOException {
+ String fileName = TEST_FILE_BINARY_SEARCH;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long firstSeekTimeUs = 1_234_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 987_00;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekContainsTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_binarySearch_handlesSeekingForward() throws IOException {
+ String fileName = TEST_FILE_BINARY_SEARCH;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
long firstSeekTimeUs = 987_000;
- seekToTimeUs(extractor, seekMap, firstSeekTimeUs, trackOutput);
-
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
long targetSeekTimeUs = 1_234_000;
- int extractedFrameIndex = seekToTimeUs(extractor, seekMap, targetSeekTimeUs, trackOutput);
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
- assertThat(extractedFrameIndex).isNotEqualTo(-1);
- assertFirstFrameAfterSeekContainTargetSeekTime(
- trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekContainsTargetSeekTime(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Test
- public void testHandlePendingSeek_handlesRandomSeeks_extractsCorrectFrame()
- throws IOException, InterruptedException {
- FlacExtractor extractor = new FlacExtractor();
+ public void flacExtractorReads_unseekable_returnUnseekableSeekMap() throws IOException {
+ Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_UNSEEKABLE);
- FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
- SeekMap seekMap = extractSeekMap(extractor, extractorOutput);
- FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
- long numSeek = 100;
- for (long i = 0; i < numSeek; i++) {
- long targetSeekTimeUs = RANDOM.nextInt(DURATION_US + 1);
- int extractedFrameIndex = seekToTimeUs(extractor, seekMap, targetSeekTimeUs, trackOutput);
-
- assertThat(extractedFrameIndex).isNotEqualTo(-1);
- assertFirstFrameAfterSeekContainTargetSeekTime(
- trackOutput, targetSeekTimeUs, extractedFrameIndex);
- }
+ assertThat(seekMap).isNotNull();
+ assertThat(seekMap.getDurationUs()).isEqualTo(C.TIME_UNSET);
+ assertThat(seekMap.isSeekable()).isFalse();
}
- // Internal methods
+ private static void assertFirstFrameAfterSeekContainsTargetSeekTime(
+ String fileName,
+ FakeTrackOutput trackOutput,
+ long targetSeekTimeUs,
+ int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ int expectedFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
- private long readInputLength() throws IOException {
- DataSpec dataSpec = new DataSpec(FILE_URI, 0, C.LENGTH_UNSET, null);
- long totalInputLength = dataSource.open(dataSpec);
- Util.closeQuietly(dataSource);
- return totalInputLength;
- }
-
- /**
- * Seeks to the given seek time and keeps reading from input until we can extract at least one
- * frame from the seek position, or until end-of-input is reached.
- *
- * @return The index of the first extracted frame written to the given {@code trackOutput} after
- * the seek is completed, or -1 if the seek is completed without any extracted frame.
- */
- private int seekToTimeUs(
- FlacExtractor flacExtractor, SeekMap seekMap, long seekTimeUs, FakeTrackOutput trackOutput)
- throws IOException, InterruptedException {
- int numSampleBeforeSeek = trackOutput.getSampleCount();
- SeekMap.SeekPoints seekPoints = seekMap.getSeekPoints(seekTimeUs);
-
- long initialSeekLoadPosition = seekPoints.first.position;
- flacExtractor.seek(initialSeekLoadPosition, seekTimeUs);
-
- positionHolder.position = C.POSITION_UNSET;
- ExtractorInput extractorInput = getExtractorInputFromPosition(initialSeekLoadPosition);
- int extractorReadResult = Extractor.RESULT_CONTINUE;
- while (true) {
- try {
- // Keep reading until we can read at least one frame after seek
- while (extractorReadResult == Extractor.RESULT_CONTINUE
- && trackOutput.getSampleCount() == numSampleBeforeSeek) {
- extractorReadResult = flacExtractor.read(extractorInput, positionHolder);
- }
- } finally {
- Util.closeQuietly(dataSource);
- }
-
- if (extractorReadResult == Extractor.RESULT_SEEK) {
- extractorInput = getExtractorInputFromPosition(positionHolder.position);
- extractorReadResult = Extractor.RESULT_CONTINUE;
- } else if (extractorReadResult == Extractor.RESULT_END_OF_INPUT) {
- return -1;
- } else if (trackOutput.getSampleCount() > numSampleBeforeSeek) {
- // First index after seek = num sample before seek.
- return numSampleBeforeSeek;
- }
- }
- }
-
- @Nullable
- private SeekMap extractSeekMap(FlacExtractor extractor, FakeExtractorOutput output)
- throws IOException, InterruptedException {
- try {
- ExtractorInput input = getExtractorInputFromPosition(0);
- extractor.init(output);
- while (output.seekMap == null) {
- extractor.read(input, positionHolder);
- }
- } finally {
- Util.closeQuietly(dataSource);
- }
- return output.seekMap;
- }
-
- private void assertFirstFrameAfterSeekContainTargetSeekTime(
- FakeTrackOutput trackOutput, long seekTimeUs, int firstFrameIndexAfterSeek) {
- int expectedSampleIndex = findTargetFrameInExpectedOutput(seekTimeUs);
- // Assert that after seeking, the first sample frame written to output contains the sample
- // at seek time.
trackOutput.assertSample(
firstFrameIndexAfterSeek,
- expectedTrackOutput.getSampleData(expectedSampleIndex),
- expectedTrackOutput.getSampleTimeUs(expectedSampleIndex),
- expectedTrackOutput.getSampleFlags(expectedSampleIndex),
- expectedTrackOutput.getSampleCryptoData(expectedSampleIndex));
+ expectedTrackOutput.getSampleData(expectedFrameIndex),
+ expectedTrackOutput.getSampleTimeUs(expectedFrameIndex),
+ expectedTrackOutput.getSampleFlags(expectedFrameIndex),
+ expectedTrackOutput.getSampleCryptoData(expectedFrameIndex));
}
- private int findTargetFrameInExpectedOutput(long seekTimeUs) {
- List<Long> sampleTimes = expectedTrackOutput.getSampleTimesUs();
- for (int i = 0; i < sampleTimes.size() - 1; i++) {
- long currentSampleTime = sampleTimes.get(i);
- long nextSampleTime = sampleTimes.get(i + 1);
- if (currentSampleTime <= seekTimeUs && nextSampleTime > seekTimeUs) {
- return i;
+ private static void assertFirstFrameAfterSeekPrecedesTargetSeekTime(
+ String fileName,
+ FakeTrackOutput trackOutput,
+ long targetSeekTimeUs,
+ int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ int maxFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
+
+ long firstFrameAfterSeekTimeUs = trackOutput.getSampleTimeUs(firstFrameIndexAfterSeek);
+ assertThat(firstFrameAfterSeekTimeUs).isAtMost(targetSeekTimeUs);
+
+ boolean frameFound = false;
+ for (int i = maxFrameIndex; i >= 0; i--) {
+ if (firstFrameAfterSeekTimeUs == expectedTrackOutput.getSampleTimeUs(i)) {
+ trackOutput.assertSample(
+ firstFrameIndexAfterSeek,
+ expectedTrackOutput.getSampleData(i),
+ expectedTrackOutput.getSampleTimeUs(i),
+ expectedTrackOutput.getSampleFlags(i),
+ expectedTrackOutput.getSampleCryptoData(i));
+ frameFound = true;
+ break;
}
}
- return sampleTimes.size() - 1;
+
+ assertThat(frameFound).isTrue();
}
- private ExtractorInput getExtractorInputFromPosition(long position) throws IOException {
- DataSpec dataSpec = new DataSpec(FILE_URI, position, totalInputLength, /* key= */ null);
- dataSource.open(dataSpec);
- return new DefaultExtractorInput(dataSource, position, totalInputLength);
+ private static FakeTrackOutput getExpectedTrackOutput(String fileName) throws IOException {
+ return TestUtil.extractAllSamplesFromFile(
+ new FlacExtractor(), ApplicationProvider.getApplicationContext(), fileName)
+ .trackOutputs
+ .get(0);
}
- private void extractAllSamplesFromFileToExpectedOutput(Context context, String fileName)
- throws IOException, InterruptedException {
- byte[] data = TestUtil.getByteArray(context, fileName);
-
- FlacExtractor extractor = new FlacExtractor();
- extractor.init(expectedOutput);
- FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
-
- while (extractor.read(input, new PositionHolder()) != Extractor.RESULT_END_OF_INPUT) {}
+ private static int getFrameIndex(FakeTrackOutput expectedTrackOutput, long targetSeekTimeUs) {
+ List<Long> frameTimes = expectedTrackOutput.getSampleTimesUs();
+ return Util.binarySearchFloor(
+ frameTimes, targetSeekTimeUs, /* inclusive= */ true, /* stayInBounds= */ false);
}
}
diff --git a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java
index c8033e0..e203849 100644
--- a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java
+++ b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java
@@ -36,14 +36,92 @@
}
@Test
- public void testExtractFlacSample() throws Exception {
+ public void sample() throws Exception {
ExtractorAsserts.assertBehavior(
- FlacExtractor::new, "bear.flac", ApplicationProvider.getApplicationContext());
+ FlacExtractor::new,
+ /* file= */ "flac/bear.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_raw");
}
@Test
- public void testExtractFlacSampleWithId3Header() throws Exception {
+ public void sampleWithId3HeaderAndId3Enabled() throws Exception {
ExtractorAsserts.assertBehavior(
- FlacExtractor::new, "bear_with_id3.flac", ApplicationProvider.getApplicationContext());
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_id3.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_id3_enabled_raw");
+ }
+
+ @Test
+ public void sampleWithId3HeaderAndId3Disabled() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ () -> new FlacExtractor(FlacExtractor.FLAG_DISABLE_ID3_METADATA),
+ /* file= */ "flac/bear_with_id3.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_id3_disabled_raw");
+ }
+
+ @Test
+ public void sampleUnseekable() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_seek_table_no_num_samples.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_seek_table_no_num_samples_raw");
+ }
+
+ @Test
+ public void sampleWithVorbisComments() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_vorbis_comments.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_vorbis_comments_raw");
+ }
+
+ @Test
+ public void sampleWithPicture() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_picture.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_picture_raw");
+ }
+
+ @Test
+ public void oneMetadataBlock() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_one_metadata_block.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_one_metadata_block_raw");
+ }
+
+ @Test
+ public void noMinMaxFrameSize() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_min_max_frame_size.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_min_max_frame_size_raw");
+ }
+
+ @Test
+ public void noNumSamples() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_num_samples.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_num_samples_raw");
+ }
+
+ @Test
+ public void uncommonSampleRate() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_uncommon_sample_rate.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_uncommon_sample_rate_raw");
}
}
diff --git a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java
index f596231..e9b1fd1 100644
--- a/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java
+++ b/tree/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.net.Uri;
import android.os.Looper;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -41,8 +42,8 @@
@RunWith(AndroidJUnit4.class)
public class FlacPlaybackTest {
- private static final String BEAR_FLAC_16BIT = "bear-flac-16bit.mka";
- private static final String BEAR_FLAC_24BIT = "bear-flac-24bit.mka";
+ private static final String BEAR_FLAC_16BIT = "mka/bear-flac-16bit.mka";
+ private static final String BEAR_FLAC_24BIT = "mka/bear-flac-24bit.mka";
@Before
public void setUp() {
@@ -88,8 +89,8 @@
private final Uri uri;
private final AudioSink audioSink;
- private ExoPlayer player;
- private ExoPlaybackException playbackException;
+ @Nullable private ExoPlayer player;
+ @Nullable private ExoPlaybackException playbackException;
public TestPlaybackRunnable(Uri uri, Context context, AudioSink audioSink) {
this.uri = uri;
@@ -121,7 +122,7 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
player.release();
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeeker.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeeker.java
index 4053c17..742ade2 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeeker.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacBinarySearchSeeker.java
@@ -100,7 +100,7 @@
@Override
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetSampleIndex)
- throws IOException, InterruptedException {
+ throws IOException {
ByteBuffer outputBuffer = outputFrameHolder.byteBuffer;
long searchPosition = input.getPosition();
decoderJni.reset(searchPosition);
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java
index 31aacfd..84cb081 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java
@@ -63,7 +63,7 @@
streamMetadata = decoderJni.decodeStreamMetadata();
} catch (ParserException e) {
throw new FlacDecoderException("Failed to decode StreamInfo", e);
- } catch (IOException | InterruptedException e) {
+ } catch (IOException e) {
// Never happens.
throw new IllegalStateException(e);
}
@@ -85,7 +85,7 @@
@Override
protected SimpleOutputBuffer createOutputBuffer() {
- return new SimpleOutputBuffer(this);
+ return new SimpleOutputBuffer(this::releaseOutputBuffer);
}
@Override
@@ -107,7 +107,7 @@
decoderJni.decodeSample(outputData);
} catch (FlacDecoderJni.FlacFrameDecodeException e) {
return new FlacDecoderException("Frame decoding failed", e);
- } catch (IOException | InterruptedException e) {
+ } catch (IOException e) {
// Never happens.
throw new IllegalStateException(e);
}
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderException.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderException.java
index 95d7f87..2c2f56e 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderException.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderException.java
@@ -15,12 +15,10 @@
*/
package com.google.android.exoplayer2.ext.flac;
-import com.google.android.exoplayer2.audio.AudioDecoderException;
+import com.google.android.exoplayer2.decoder.DecoderException;
-/**
- * Thrown when an Flac decoder error occurs.
- */
-public final class FlacDecoderException extends AudioDecoderException {
+/** Thrown when an Flac decoder error occurs. */
+public final class FlacDecoderException extends DecoderException {
/* package */ FlacDecoderException(String message) {
super(message);
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java
index 6e8f394..daf4584 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoderJni.java
@@ -115,7 +115,7 @@
* read from the source, then 0 is returned.
*/
@SuppressWarnings("unused") // Called from native code.
- public int read(ByteBuffer target) throws IOException, InterruptedException {
+ public int read(ByteBuffer target) throws IOException {
int byteCount = target.remaining();
if (byteBufferData != null) {
byteCount = Math.min(byteCount, byteBufferData.remaining());
@@ -145,7 +145,7 @@
}
/** Decodes and consumes the metadata from the FLAC stream. */
- public FlacStreamMetadata decodeStreamMetadata() throws IOException, InterruptedException {
+ public FlacStreamMetadata decodeStreamMetadata() throws IOException {
FlacStreamMetadata streamMetadata = flacDecodeMetadata(nativeDecoderContext);
if (streamMetadata == null) {
throw new ParserException("Failed to decode stream metadata");
@@ -161,7 +161,7 @@
* @param retryPosition If any error happens, the input will be rewound to {@code retryPosition}.
*/
public void decodeSampleWithBacktrackPosition(ByteBuffer output, long retryPosition)
- throws InterruptedException, IOException, FlacFrameDecodeException {
+ throws IOException, FlacFrameDecodeException {
try {
decodeSample(output);
} catch (IOException e) {
@@ -177,8 +177,7 @@
/** Decodes and consumes the next sample from the FLAC stream into the given byte buffer. */
@SuppressWarnings("ByteBufferBackingArray")
- public void decodeSample(ByteBuffer output)
- throws IOException, InterruptedException, FlacFrameDecodeException {
+ public void decodeSample(ByteBuffer output) throws IOException, FlacFrameDecodeException {
output.clear();
int frameSize =
output.isDirect()
@@ -266,8 +265,7 @@
}
private int readFromExtractorInput(
- ExtractorInput extractorInput, byte[] tempBuffer, int offset, int length)
- throws IOException, InterruptedException {
+ ExtractorInput extractorInput, byte[] tempBuffer, int offset, int length) throws IOException {
int read = extractorInput.read(tempBuffer, offset, length);
if (read == C.RESULT_END_OF_INPUT) {
endOfExtractorInput = true;
@@ -278,14 +276,11 @@
private native long flacInit();
- private native FlacStreamMetadata flacDecodeMetadata(long context)
- throws IOException, InterruptedException;
+ private native FlacStreamMetadata flacDecodeMetadata(long context) throws IOException;
- private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer)
- throws IOException, InterruptedException;
+ private native int flacDecodeToBuffer(long context, ByteBuffer outputBuffer) throws IOException;
- private native int flacDecodeToArray(long context, byte[] outputArray)
- throws IOException, InterruptedException;
+ private native int flacDecodeToArray(long context, byte[] outputArray) throws IOException;
private native long flacGetDecodePosition(long context);
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
index 922680e..364cf80 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
@@ -113,14 +113,13 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
id3Metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ !id3MetadataDisabled);
return FlacMetadataReader.checkAndPeekStreamMarker(input);
}
@Override
- public int read(final ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(final ExtractorInput input, PositionHolder seekPosition) throws IOException {
if (input.getPosition() == 0 && !id3MetadataDisabled && id3Metadata == null) {
id3Metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ true);
}
@@ -185,7 +184,7 @@
@RequiresNonNull({"decoderJni", "extractorOutput", "trackOutput"}) // Requires initialized.
@EnsuresNonNull({"streamMetadata", "outputFrameHolder"}) // Ensures stream metadata decoded.
@SuppressWarnings({"contracts.postcondition.not.satisfied"})
- private void decodeStreamMetadata(ExtractorInput input) throws InterruptedException, IOException {
+ private void decodeStreamMetadata(ExtractorInput input) throws IOException {
if (streamMetadataDecoded) {
return;
}
@@ -225,7 +224,7 @@
ParsableByteArray outputBuffer,
OutputFrameHolder outputFrameHolder,
TrackOutput trackOutput)
- throws InterruptedException, IOException {
+ throws IOException {
int seekResult = binarySearchSeeker.handlePendingSeek(input, seekPosition);
ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer;
if (seekResult == RESULT_CONTINUE && outputByteBuffer.limit() > 0) {
@@ -250,7 +249,7 @@
SeekMap seekMap;
if (haveSeekTable) {
seekMap = new FlacSeekMap(streamMetadata.getDurationUs(), decoderJni);
- } else if (streamLength != C.LENGTH_UNSET) {
+ } else if (streamLength != C.LENGTH_UNSET && streamMetadata.totalSamples > 0) {
long firstFramePosition = decoderJni.getDecodePosition();
binarySearchSeeker =
new FlacBinarySearchSeeker(
@@ -266,22 +265,16 @@
private static void outputFormat(
FlacStreamMetadata streamMetadata, @Nullable Metadata metadata, TrackOutput output) {
Format mediaFormat =
- Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- streamMetadata.getBitRate(),
- streamMetadata.getMaxDecodedFrameSize(),
- streamMetadata.channels,
- streamMetadata.sampleRate,
- getPcmEncoding(streamMetadata.bitsPerSample),
- /* encoderDelay= */ 0,
- /* encoderPadding= */ 0,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null,
- metadata);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setAverageBitrate(streamMetadata.getDecodedBitrate())
+ .setPeakBitrate(streamMetadata.getDecodedBitrate())
+ .setMaxInputSize(streamMetadata.getMaxDecodedFrameSize())
+ .setChannelCount(streamMetadata.channels)
+ .setSampleRate(streamMetadata.sampleRate)
+ .setPcmEncoding(getPcmEncoding(streamMetadata.bitsPerSample))
+ .setMetadata(metadata)
+ .build();
output.format(mediaFormat);
}
diff --git a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java
index 6b01fd4..cbdf42d 100644
--- a/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java
+++ b/tree/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java
@@ -22,19 +22,20 @@
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioSink;
-import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.audio.DecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.FlacStreamMetadata;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacConstants;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Decodes and renders audio using the native Flac decoder. */
-public final class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
+public final class LibflacAudioRenderer extends DecoderAudioRenderer {
+ private static final String TAG = "LibflacAudioRenderer";
private static final int NUM_BUFFERS = 16;
private @MonotonicNonNull FlacStreamMetadata streamMetadata;
@@ -69,15 +70,17 @@
super(
eventHandler,
eventListener,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
audioSink);
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@FormatSupport
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
+ protected int supportsFormatInternal(Format format) {
if (!FlacLibrary.isAvailable()
|| !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
@@ -99,7 +102,7 @@
}
if (!supportsOutput(format.channelCount, pcmEncoding)) {
return FORMAT_UNSUPPORTED_SUBTYPE;
- } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
+ } else if (format.drmInitData != null && format.exoMediaCryptoType == null) {
return FORMAT_UNSUPPORTED_DRM;
} else {
return FORMAT_HANDLED;
@@ -109,27 +112,22 @@
@Override
protected FlacDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws FlacDecoderException {
+ TraceUtil.beginSection("createFlacDecoder");
FlacDecoder decoder =
new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.maxInputSize, format.initializationData);
streamMetadata = decoder.getStreamMetadata();
+ TraceUtil.endSection();
return decoder;
}
@Override
protected Format getOutputFormat() {
Assertions.checkNotNull(streamMetadata);
- return Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- streamMetadata.channels,
- streamMetadata.sampleRate,
- Util.getPcmEncoding(streamMetadata.bitsPerSample),
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setChannelCount(streamMetadata.channels)
+ .setSampleRate(streamMetadata.sampleRate)
+ .setPcmEncoding(Util.getPcmEncoding(streamMetadata.bitsPerSample))
+ .build();
}
}
diff --git a/tree/extensions/flac/src/main/jni/flac_parser.cc b/tree/extensions/flac/src/main/jni/flac_parser.cc
index f39e4bd..c368647 100644
--- a/tree/extensions/flac/src/main/jni/flac_parser.cc
+++ b/tree/extensions/flac/src/main/jni/flac_parser.cc
@@ -349,26 +349,6 @@
ALOGE("unsupported bits per sample %u", getBitsPerSample());
return false;
}
- // check sample rate
- switch (getSampleRate()) {
- case 8000:
- case 11025:
- case 12000:
- case 16000:
- case 22050:
- case 24000:
- case 32000:
- case 44100:
- case 48000:
- case 88200:
- case 96000:
- case 176400:
- case 192000:
- break;
- default:
- ALOGE("unsupported sample rate %u", getSampleRate());
- return false;
- }
// configure the appropriate copy function based on device endianness.
if (isBigEndian()) {
mCopy = copyToByteArrayBigEndian;
diff --git a/tree/extensions/gvr/build.gradle b/tree/extensions/gvr/build.gradle
index f899261..4e6bd76 100644
--- a/tree/extensions/gvr/build.gradle
+++ b/tree/extensions/gvr/build.gradle
@@ -36,6 +36,7 @@
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
api 'com.google.vr:sdk-base:1.190.0'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java b/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java
index 8ba3329..1457075 100644
--- a/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java
+++ b/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java
@@ -32,7 +32,7 @@
* href="https://github.com/google/ExoPlayer/issues">issue tracker</a>.
*/
@Deprecated
-public final class GvrAudioProcessor implements AudioProcessor {
+public class GvrAudioProcessor implements AudioProcessor {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.gvr");
diff --git a/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/package-info.java b/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/package-info.java
new file mode 100644
index 0000000..155317f
--- /dev/null
+++ b/tree/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+package com.google.android.exoplayer2.ext.gvr;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/extensions/ima/README.md b/tree/extensions/ima/README.md
index 4ed6a54..f28ba29 100644
--- a/tree/extensions/ima/README.md
+++ b/tree/extensions/ima/README.md
@@ -58,7 +58,9 @@
## Links ##
+* [ExoPlayer documentation on ad insertion][]
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*`
belong to this module.
+[ExoPlayer documentation on ad insertion]: https://exoplayer.dev/ad-insertion.html
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
diff --git a/tree/extensions/ima/build.gradle b/tree/extensions/ima/build.gradle
index e2292ae..1bd0b8d 100644
--- a/tree/extensions/ima/build.gradle
+++ b/tree/extensions/ima/build.gradle
@@ -26,6 +26,13 @@
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt'
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ // Enable multidex for androidTests.
+ multiDexEnabled true
+ }
+
+ sourceSets {
+ androidTest.assets.srcDir '../../testdata/src/test/assets/'
}
testOptions.unitTests.includeAndroidResources = true
@@ -36,6 +43,12 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
+ androidTestImplementation project(modulePrefix + 'testutils')
+ androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
+ androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
+ androidTestImplementation 'com.android.support:multidex:1.0.3'
+ androidTestCompileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/extensions/ima/src/androidTest/AndroidManifest.xml b/tree/extensions/ima/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..c8bd575
--- /dev/null
+++ b/tree/extensions/ima/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.google.android.exoplayer2.ext.ima.test">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-sdk/>
+
+ <application
+ android:allowBackup="false"
+ tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
+ <activity android:name="com.google.android.exoplayer2.testutil.HostActivity"
+ android:configChanges="keyboardHidden|orientation|screenSize"
+ android:label="ExoPlayerTest"/>
+ </application>
+
+ <instrumentation
+ android:targetPackage="com.google.android.exoplayer2.ext.ima.test"
+ android:name="androidx.test.runner.AndroidJUnitRunner"/>
+
+</manifest>
diff --git a/tree/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java b/tree/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java
new file mode 100644
index 0000000..ed9130b
--- /dev/null
+++ b/tree/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.ext.ima;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Player.DiscontinuityReason;
+import com.google.android.exoplayer2.Player.EventListener;
+import com.google.android.exoplayer2.Player.TimelineChangeReason;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline.Window;
+import com.google.android.exoplayer2.analytics.AnalyticsListener;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider;
+import com.google.android.exoplayer2.source.ads.AdsMediaSource;
+import com.google.android.exoplayer2.testutil.ExoHostedTest;
+import com.google.android.exoplayer2.testutil.HostActivity;
+import com.google.android.exoplayer2.testutil.TestUtil;
+import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Playback tests using {@link ImaAdsLoader}. */
+@RunWith(AndroidJUnit4.class)
+public final class ImaPlaybackTest {
+
+ private static final long TIMEOUT_MS = 5 * 60 * C.MILLIS_PER_SECOND;
+
+ private static final String CONTENT_URI =
+ "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4";
+ private static final String PREROLL_ADS_RESPONSE_FILE_NAME = "ad-responses/preroll.xml";
+ private static final String MIDROLL_ADS_RESPONSE_FILE_NAME = "ad-responses/midroll.xml";
+
+ private static final AdId CONTENT = new AdId(C.INDEX_UNSET, C.INDEX_UNSET);
+
+ @Rule public ActivityTestRule<HostActivity> testRule = new ActivityTestRule<>(HostActivity.class);
+
+ @Test
+ public void playbackWithPrerollAdTag_playsAdAndContent() throws Exception {
+ AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT};
+ String adsResponse =
+ TestUtil.getString(/* context= */ testRule.getActivity(), PREROLL_ADS_RESPONSE_FILE_NAME);
+ ImaHostedTest hostedTest =
+ new ImaHostedTest(Uri.parse(CONTENT_URI), adsResponse, expectedAdIds);
+
+ testRule.getActivity().runTest(hostedTest, TIMEOUT_MS);
+ }
+
+ @Test
+ public void playbackWithMidrolls_playsAdAndContent() throws Exception {
+ AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT, ad(1), CONTENT, ad(2), CONTENT};
+ String adsResponse =
+ TestUtil.getString(/* context= */ testRule.getActivity(), MIDROLL_ADS_RESPONSE_FILE_NAME);
+ ImaHostedTest hostedTest =
+ new ImaHostedTest(Uri.parse(CONTENT_URI), adsResponse, expectedAdIds);
+
+ testRule.getActivity().runTest(hostedTest, TIMEOUT_MS);
+ }
+
+ private static AdId ad(int groupIndex) {
+ return new AdId(groupIndex, /* indexInGroup= */ 0);
+ }
+
+ private static final class AdId {
+
+ public final int groupIndex;
+ public final int indexInGroup;
+
+ public AdId(int groupIndex, int indexInGroup) {
+ this.groupIndex = groupIndex;
+ this.indexInGroup = indexInGroup;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + groupIndex + ", " + indexInGroup + ')';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AdId that = (AdId) o;
+
+ if (groupIndex != that.groupIndex) {
+ return false;
+ }
+ return indexInGroup == that.indexInGroup;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = groupIndex;
+ result = 31 * result + indexInGroup;
+ return result;
+ }
+ }
+
+ private static final class ImaHostedTest extends ExoHostedTest implements EventListener {
+
+ private final Uri contentUri;
+ private final String adsResponse;
+ private final List<AdId> expectedAdIds;
+ private final List<AdId> seenAdIds;
+ private @MonotonicNonNull ImaAdsLoader imaAdsLoader;
+ private @MonotonicNonNull SimpleExoPlayer player;
+
+ private ImaHostedTest(Uri contentUri, String adsResponse, AdId... expectedAdIds) {
+ // fullPlaybackNoSeeking is false as the playback lasts longer than the content source
+ // duration due to ad playback, so the hosted test shouldn't assert the playing duration.
+ super(ImaPlaybackTest.class.getSimpleName(), /* fullPlaybackNoSeeking= */ false);
+ this.contentUri = contentUri;
+ this.adsResponse = adsResponse;
+ this.expectedAdIds = Arrays.asList(expectedAdIds);
+ seenAdIds = new ArrayList<>();
+ }
+
+ @Override
+ protected SimpleExoPlayer buildExoPlayer(
+ HostActivity host, Surface surface, MappingTrackSelector trackSelector) {
+ player = super.buildExoPlayer(host, surface, trackSelector);
+ player.addAnalyticsListener(
+ new AnalyticsListener() {
+ @Override
+ public void onTimelineChanged(EventTime eventTime, @TimelineChangeReason int reason) {
+ maybeUpdateSeenAdIdentifiers();
+ }
+
+ @Override
+ public void onPositionDiscontinuity(
+ EventTime eventTime, @DiscontinuityReason int reason) {
+ maybeUpdateSeenAdIdentifiers();
+ }
+ });
+ Context context = host.getApplicationContext();
+ imaAdsLoader = new ImaAdsLoader.Builder(context).buildForAdsResponse(adsResponse);
+ imaAdsLoader.setPlayer(player);
+ return player;
+ }
+
+ @Override
+ protected MediaSource buildSource(
+ HostActivity host,
+ String userAgent,
+ DrmSessionManager drmSessionManager,
+ FrameLayout overlayFrameLayout) {
+ Context context = host.getApplicationContext();
+ DataSource.Factory dataSourceFactory =
+ new DefaultDataSourceFactory(
+ context, Util.getUserAgent(context, ImaPlaybackTest.class.getSimpleName()));
+ MediaSource contentMediaSource =
+ DefaultMediaSourceFactory.newInstance(context)
+ .createMediaSource(MediaItem.fromUri(contentUri));
+ return new AdsMediaSource(
+ contentMediaSource,
+ dataSourceFactory,
+ Assertions.checkNotNull(imaAdsLoader),
+ new AdViewProvider() {
+ @Override
+ public ViewGroup getAdViewGroup() {
+ return overlayFrameLayout;
+ }
+
+ @Override
+ public View[] getAdOverlayViews() {
+ return new View[0];
+ }
+ });
+ }
+
+ @Override
+ protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) {
+ assertThat(seenAdIds).isEqualTo(expectedAdIds);
+ }
+
+ private void maybeUpdateSeenAdIdentifiers() {
+ if (Assertions.checkNotNull(player)
+ .getCurrentTimeline()
+ .getWindow(/* windowIndex= */ 0, new Window())
+ .isPlaceholder) {
+ // The window is still an initial placeholder so do nothing.
+ return;
+ }
+ AdId adId = new AdId(player.getCurrentAdGroupIndex(), player.getCurrentAdIndexInAdGroup());
+ if (seenAdIds.isEmpty() || !seenAdIds.get(seenAdIds.size() - 1).equals(adId)) {
+ seenAdIds.add(adId);
+ }
+ }
+ }
+}
diff --git a/tree/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/tree/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
index fd777a9..04a12e5 100644
--- a/tree/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
+++ b/tree/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
@@ -332,7 +332,7 @@
private VideoProgressUpdate lastAdProgress;
private int lastVolumePercentage;
- private AdsManager adsManager;
+ @Nullable private AdsManager adsManager;
private boolean initializedAdsManager;
private AdLoadException pendingAdLoadError;
private Timeline timeline;
@@ -755,14 +755,16 @@
sentPendingContentPositionMs = true;
contentPositionMs = pendingContentPositionMs;
expectedAdGroupIndex =
- adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
+ adPlaybackState.getAdGroupIndexForPositionUs(
+ C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
} else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs;
expectedAdGroupIndex =
- adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
+ adPlaybackState.getAdGroupIndexForPositionUs(
+ C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
} else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) {
- contentPositionMs = player.getCurrentPosition();
+ contentPositionMs = getContentPeriodPositionMs();
// Update the expected ad group index for the current content position. The update is delayed
// until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered
// just after an ad group isn't incorrectly attributed to the next ad group.
@@ -964,7 +966,7 @@
}
Assertions.checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline;
- long contentDurationUs = timeline.getPeriod(0, period).durationUs;
+ long contentDurationUs = timeline.getPeriod(/* periodIndex= */ 0, period).durationUs;
contentDurationMs = C.usToMs(contentDurationUs);
if (contentDurationUs != C.TIME_UNSET) {
adPlaybackState = adPlaybackState.withContentDurationUs(contentDurationUs);
@@ -973,12 +975,21 @@
initializedAdsManager = true;
initializeAdsManager();
}
- onPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
+ checkForContentCompleteOrNewAdGroup();
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
- if (adsManager == null) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
+ if (adsManager == null || player == null) {
+ return;
+ }
+ handlePlayerStateChanged(player.getPlayWhenReady(), playbackState);
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ if (adsManager == null || player == null) {
return;
}
@@ -991,18 +1002,7 @@
adsManager.resume();
return;
}
-
- if (imaAdState == IMA_AD_STATE_NONE && playbackState == Player.STATE_BUFFERING
- && playWhenReady) {
- checkForContentComplete();
- } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) {
- for (int i = 0; i < adCallbacks.size(); i++) {
- adCallbacks.get(i).onEnded();
- }
- if (DEBUG) {
- Log.d(TAG, "VideoAdPlayerCallback.onEnded in onPlayerStateChanged");
- }
- }
+ handlePlayerStateChanged(playWhenReady, player.getPlaybackState());
}
@Override
@@ -1016,32 +1016,7 @@
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
- if (adsManager == null) {
- return;
- }
- if (!playingAd && !player.isPlayingAd()) {
- checkForContentComplete();
- if (sentContentComplete) {
- for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
- if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
- adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
- }
- }
- updateAdPlaybackState();
- } else if (!timeline.isEmpty()) {
- long positionMs = player.getCurrentPosition();
- timeline.getPeriod(0, period);
- int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs));
- if (newAdGroupIndex != C.INDEX_UNSET) {
- sentPendingContentPositionMs = false;
- pendingContentPositionMs = positionMs;
- if (newAdGroupIndex != adGroupIndex) {
- shouldNotifyAdPrepareError = false;
- }
- }
- }
- }
- updateImaStateForPlayerState();
+ checkForContentCompleteOrNewAdGroup();
}
// Internal methods.
@@ -1063,9 +1038,10 @@
// Skip ads based on the start position as required.
long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints());
- long contentPositionMs = player.getContentPosition();
+ long contentPositionMs = getContentPeriodPositionMs();
int adGroupIndexForPosition =
- adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
+ adPlaybackState.getAdGroupIndexForPositionUs(
+ C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
if (adGroupIndexForPosition > 0 && adGroupIndexForPosition != C.INDEX_UNSET) {
// Skip any ad groups before the one at or immediately before the playback position.
for (int i = 0; i < adGroupIndexForPosition; i++) {
@@ -1174,6 +1150,50 @@
}
}
+ private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ if (imaAdState == IMA_AD_STATE_NONE
+ && playbackState == Player.STATE_BUFFERING
+ && playWhenReady) {
+ checkForContentComplete();
+ } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) {
+ for (int i = 0; i < adCallbacks.size(); i++) {
+ adCallbacks.get(i).onEnded();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "VideoAdPlayerCallback.onEnded in onPlaybackStateChanged");
+ }
+ }
+ }
+
+ private void checkForContentCompleteOrNewAdGroup() {
+ if (adsManager == null || player == null) {
+ return;
+ }
+ if (!playingAd && !player.isPlayingAd()) {
+ checkForContentComplete();
+ if (sentContentComplete) {
+ for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
+ if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
+ adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ i);
+ }
+ }
+ updateAdPlaybackState();
+ } else if (!timeline.isEmpty()) {
+ long positionMs = getContentPeriodPositionMs();
+ timeline.getPeriod(/* periodIndex= */ 0, period);
+ int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs));
+ if (newAdGroupIndex != C.INDEX_UNSET) {
+ sentPendingContentPositionMs = false;
+ pendingContentPositionMs = positionMs;
+ if (newAdGroupIndex != adGroupIndex) {
+ shouldNotifyAdPrepareError = false;
+ }
+ }
+ }
+ }
+ updateImaStateForPlayerState();
+ }
+
private void updateImaStateForPlayerState() {
boolean wasPlayingAd = playingAd;
int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup;
@@ -1301,8 +1321,9 @@
}
private void checkForContentComplete() {
- if (contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET
- && player.getContentPosition() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs
+ if (contentDurationMs != C.TIME_UNSET
+ && pendingContentPositionMs == C.TIME_UNSET
+ && getContentPeriodPositionMs() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs
&& !sentContentComplete) {
adsLoader.contentComplete();
if (DEBUG) {
@@ -1312,7 +1333,8 @@
// After sending content complete IMA will not poll the content position, so set the expected
// ad group index.
expectedAdGroupIndex =
- adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentDurationMs));
+ adPlaybackState.getAdGroupIndexForPositionUs(
+ C.msToUs(contentDurationMs), C.msToUs(contentDurationMs));
}
}
@@ -1364,6 +1386,12 @@
}
}
+ private long getContentPeriodPositionMs() {
+ long contentWindowPositionMs = player.getContentPosition();
+ return contentWindowPositionMs
+ - timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs();
+ }
+
private static long[] getAdGroupTimesUs(List<Float> cuePoints) {
if (cuePoints.isEmpty()) {
// If no cue points are specified, there is a preroll ad.
diff --git a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
index e3af8db..4c98233 100644
--- a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
+++ b/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
@@ -92,13 +92,22 @@
}
/** Sets the {@link Player.State} of this player. */
+ @SuppressWarnings("deprecation")
public void setState(@Player.State int state, boolean playWhenReady) {
- boolean notify = this.state != state || this.playWhenReady != playWhenReady;
+ boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
+ boolean playbackStateChanged = this.state != state;
this.state = state;
this.playWhenReady = playWhenReady;
- if (notify) {
+ if (playbackStateChanged || playWhenReadyChanged) {
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, state);
+ if (playbackStateChanged) {
+ listener.onPlaybackStateChanged(state);
+ }
+ if (playWhenReadyChanged) {
+ listener.onPlayWhenReadyChanged(
+ playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
+ }
}
}
}
diff --git a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
index 2452da4..44395fa 100644
--- a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
+++ b/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.ima;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
@@ -39,11 +40,15 @@
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.SinglePeriodTimeline;
+import com.google.android.exoplayer2.Timeline.Period;
+import com.google.android.exoplayer2.ext.ima.ImaAdsLoader.ImaFactory;
+import com.google.android.exoplayer2.source.MaskingMediaSource.DummyTimeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline;
+import com.google.android.exoplayer2.testutil.FakeTimeline;
+import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException;
import java.util.Arrays;
@@ -63,8 +68,11 @@
private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND;
private static final Timeline CONTENT_TIMELINE =
- new SinglePeriodTimeline(
- CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false);
+ new FakeTimeline(
+ new TimelineWindowDefinition(
+ /* isSeekable= */ true, /* isDynamic= */ false, CONTENT_DURATION_US));
+ private static final long CONTENT_PERIOD_DURATION_US =
+ CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs;
private static final Uri TEST_URI = Uri.EMPTY;
private static final long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
private static final long[][] PREROLL_ADS_DURATIONS_US = new long[][] {{TEST_AD_DURATION_US}};
@@ -72,11 +80,11 @@
private static final FakeAd UNSKIPPABLE_AD =
new FakeAd(/* skippable= */ false, /* podIndex= */ 0, /* totalAds= */ 1, /* adPosition= */ 1);
- private @Mock ImaSdkSettings imaSdkSettings;
- private @Mock AdsRenderingSettings adsRenderingSettings;
- private @Mock AdDisplayContainer adDisplayContainer;
- private @Mock AdsManager adsManager;
- private SingletonImaFactory testImaFactory;
+ @Mock private ImaSdkSettings imaSdkSettings;
+ @Mock private AdsRenderingSettings adsRenderingSettings;
+ @Mock private AdDisplayContainer adDisplayContainer;
+ @Mock private AdsManager adsManager;
+ @Mock private ImaFactory mockImaFactory;
private ViewGroup adViewGroup;
private View adOverlayView;
private AdsLoader.AdViewProvider adViewProvider;
@@ -89,13 +97,11 @@
MockitoAnnotations.initMocks(this);
FakeAdsRequest fakeAdsRequest = new FakeAdsRequest();
FakeAdsLoader fakeAdsLoader = new FakeAdsLoader(imaSdkSettings, adsManager);
- testImaFactory =
- new SingletonImaFactory(
- imaSdkSettings,
- adsRenderingSettings,
- adDisplayContainer,
- fakeAdsRequest,
- fakeAdsLoader);
+ when(mockImaFactory.createAdDisplayContainer()).thenReturn(adDisplayContainer);
+ when(mockImaFactory.createAdsRenderingSettings()).thenReturn(adsRenderingSettings);
+ when(mockImaFactory.createAdsRequest()).thenReturn(fakeAdsRequest);
+ when(mockImaFactory.createImaSdkSettings()).thenReturn(imaSdkSettings);
+ when(mockImaFactory.createAdsLoader(any(), any(), any())).thenReturn(fakeAdsLoader);
adViewGroup = new FrameLayout(ApplicationProvider.getApplicationContext());
adOverlayView = new View(ApplicationProvider.getApplicationContext());
adViewProvider =
@@ -120,7 +126,7 @@
}
@Test
- public void testBuilder_overridesPlayerType() {
+ public void builder_overridesPlayerType() {
when(imaSdkSettings.getPlayerType()).thenReturn("test player type");
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
@@ -128,7 +134,7 @@
}
@Test
- public void testStart_setsAdUiViewGroup() {
+ public void start_setsAdUiViewGroup() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@@ -137,26 +143,36 @@
}
@Test
- public void testStart_updatesAdPlaybackState() {
+ public void start_withPlaceholderContent_initializedAdsLoader() {
+ Timeline placeholderTimeline = new DummyTimeline(/* tag= */ null);
+ setupPlayback(placeholderTimeline, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
+
+ // We'll only create the rendering settings when initializing the ads loader.
+ verify(mockImaFactory).createAdsRenderingSettings();
+ }
+
+ @Test
+ public void start_updatesAdPlaybackState() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
- new AdPlaybackState(/* adGroupTimesUs= */ 0)
+ new AdPlaybackState(/* adGroupTimesUs...= */ 0)
.withAdDurationsUs(PREROLL_ADS_DURATIONS_US)
- .withContentDurationUs(CONTENT_DURATION_US));
+ .withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@Test
- public void testStartAfterRelease() {
+ public void startAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release();
imaAdsLoader.start(adsLoaderListener, adViewProvider);
}
@Test
- public void testStartAndCallbacksAfterRelease() {
+ public void startAndCallbacksAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release();
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@@ -183,7 +199,7 @@
}
@Test
- public void testPlayback_withPrerollAd_marksAdAsPlayed() {
+ public void playback_withPrerollAd_marksAdAsPlayed() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
// Load the preroll ad.
@@ -213,8 +229,8 @@
// Verify that the preroll ad has been marked as played.
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
- new AdPlaybackState(/* adGroupTimesUs= */ 0)
- .withContentDurationUs(CONTENT_DURATION_US)
+ new AdPlaybackState(/* adGroupTimesUs...= */ 0)
+ .withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* uri= */ TEST_URI)
.withAdDurationsUs(PREROLL_ADS_DURATIONS_US)
@@ -223,7 +239,7 @@
}
@Test
- public void testStop_unregistersAllVideoControlOverlays() {
+ public void stop_unregistersAllVideoControlOverlays() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
imaAdsLoader.requestAds(adViewGroup);
@@ -240,7 +256,7 @@
when(adsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
imaAdsLoader =
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
- .setImaFactory(testImaFactory)
+ .setImaFactory(mockImaFactory)
.setImaSdkSettings(imaSdkSettings)
.buildForAdTag(TEST_URI);
imaAdsLoader.setPlayer(fakeExoPlayer);
diff --git a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java b/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java
deleted file mode 100644
index 4efd8cf..0000000
--- a/tree/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.ext.ima;
-
-import android.content.Context;
-import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
-import com.google.ads.interactivemedia.v3.api.AdsLoader;
-import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
-import com.google.ads.interactivemedia.v3.api.AdsRequest;
-import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
-
-/** {@link ImaAdsLoader.ImaFactory} that returns provided instances from each getter, for tests. */
-final class SingletonImaFactory implements ImaAdsLoader.ImaFactory {
-
- private final ImaSdkSettings imaSdkSettings;
- private final AdsRenderingSettings adsRenderingSettings;
- private final AdDisplayContainer adDisplayContainer;
- private final AdsRequest adsRequest;
- private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
-
- public SingletonImaFactory(
- ImaSdkSettings imaSdkSettings,
- AdsRenderingSettings adsRenderingSettings,
- AdDisplayContainer adDisplayContainer,
- AdsRequest adsRequest,
- com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader) {
- this.imaSdkSettings = imaSdkSettings;
- this.adsRenderingSettings = adsRenderingSettings;
- this.adDisplayContainer = adDisplayContainer;
- this.adsRequest = adsRequest;
- this.adsLoader = adsLoader;
- }
-
- @Override
- public ImaSdkSettings createImaSdkSettings() {
- return imaSdkSettings;
- }
-
- @Override
- public AdsRenderingSettings createAdsRenderingSettings() {
- return adsRenderingSettings;
- }
-
- @Override
- public AdDisplayContainer createAdDisplayContainer() {
- return adDisplayContainer;
- }
-
- @Override
- public AdsRequest createAdsRequest() {
- return adsRequest;
- }
-
- @Override
- public AdsLoader createAdsLoader(
- Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
- return adsLoader;
- }
-}
diff --git a/tree/extensions/jobdispatcher/build.gradle b/tree/extensions/jobdispatcher/build.gradle
index d7f19d2..05ac82b 100644
--- a/tree/extensions/jobdispatcher/build.gradle
+++ b/tree/extensions/jobdispatcher/build.gradle
@@ -35,6 +35,7 @@
dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'com.firebase:firebase-jobdispatcher:0.8.5'
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/leanback/build.gradle b/tree/extensions/leanback/build.gradle
index f0be172..19b4cde 100644
--- a/tree/extensions/leanback/build.gradle
+++ b/tree/extensions/leanback/build.gradle
@@ -34,6 +34,7 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.leanback:leanback:1.0.0'
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/tree/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java
index 7c2285c..e385cd5 100644
--- a/tree/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java
+++ b/tree/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java
@@ -272,7 +272,7 @@
// Player.EventListener implementation.
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
notifyStateChanged();
}
diff --git a/tree/extensions/mediasession/build.gradle b/tree/extensions/mediasession/build.gradle
index 537c5ba..f32ef26 100644
--- a/tree/extensions/mediasession/build.gradle
+++ b/tree/extensions/mediasession/build.gradle
@@ -34,6 +34,7 @@
implementation project(modulePrefix + 'library-core')
api 'androidx.media:media:' + androidxMediaVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
index 6ae35d8..913c1d8 100644
--- a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
+++ b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
@@ -38,7 +38,6 @@
import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
@@ -127,21 +126,11 @@
/** The default playback actions. */
@PlaybackActions public static final long DEFAULT_PLAYBACK_ACTIONS = ALL_PLAYBACK_ACTIONS;
- /** The default fast forward increment, in milliseconds. */
- public static final int DEFAULT_FAST_FORWARD_MS = 15000;
- /** The default rewind increment, in milliseconds. */
- public static final int DEFAULT_REWIND_MS = 5000;
-
/**
* The name of the {@link PlaybackStateCompat} float extra with the value of {@link
- * PlaybackParameters#speed}.
+ * Player#getPlaybackSpeed()}.
*/
public static final String EXTRAS_SPEED = "EXO_SPEED";
- /**
- * The name of the {@link PlaybackStateCompat} float extra with the value of {@link
- * PlaybackParameters#pitch}.
- */
- public static final String EXTRAS_PITCH = "EXO_PITCH";
private static final long BASE_PLAYBACK_ACTIONS =
PlaybackStateCompat.ACTION_PLAY_PAUSE
@@ -178,8 +167,8 @@
Player player,
ControlDispatcher controlDispatcher,
String command,
- Bundle extras,
- ResultReceiver cb);
+ @Nullable Bundle extras,
+ @Nullable ResultReceiver cb);
}
/** Interface to which playback preparation and play actions are delegated. */
@@ -394,6 +383,7 @@
* @param player The player connected to the media session.
* @return The custom action to be included in the session playback state or {@code null}.
*/
+ @Nullable
PlaybackStateCompat.CustomAction getCustomAction(Player player);
}
@@ -439,8 +429,6 @@
@Nullable private MediaButtonEventHandler mediaButtonEventHandler;
private long enabledPlaybackActions;
- private int rewindMs;
- private int fastForwardMs;
/**
* Creates an instance.
@@ -460,8 +448,6 @@
new DefaultMediaMetadataProvider(
mediaSession.getController(), /* metadataExtrasPrefix= */ null);
enabledPlaybackActions = DEFAULT_PLAYBACK_ACTIONS;
- rewindMs = DEFAULT_REWIND_MS;
- fastForwardMs = DEFAULT_FAST_FORWARD_MS;
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaSession.setCallback(componentListener, new Handler(looper));
}
@@ -503,13 +489,12 @@
/**
* Sets the {@link ControlDispatcher}.
*
- * @param controlDispatcher The {@link ControlDispatcher}, or null to use {@link
- * DefaultControlDispatcher}.
+ * @param controlDispatcher The {@link ControlDispatcher}.
*/
- public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) {
+ public void setControlDispatcher(ControlDispatcher controlDispatcher) {
if (this.controlDispatcher != controlDispatcher) {
- this.controlDispatcher =
- controlDispatcher == null ? new DefaultControlDispatcher() : controlDispatcher;
+ this.controlDispatcher = controlDispatcher;
+ invalidateMediaSessionPlaybackState();
}
}
@@ -550,27 +535,27 @@
}
/**
- * Sets the rewind increment in milliseconds.
- *
- * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
- * rewind button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)} instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setRewindIncrementMs(int rewindMs) {
- if (this.rewindMs != rewindMs) {
- this.rewindMs = rewindMs;
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setRewindIncrementMs(rewindMs);
invalidateMediaSessionPlaybackState();
}
}
/**
- * Sets the fast forward increment in milliseconds.
- *
- * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
- * cause the fast forward button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)} instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setFastForwardIncrementMs(int fastForwardMs) {
- if (this.fastForwardMs != fastForwardMs) {
- this.fastForwardMs = fastForwardMs;
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setFastForwardIncrementMs(fastForwardMs);
invalidateMediaSessionPlaybackState();
}
}
@@ -761,7 +746,7 @@
customActionMap = Collections.unmodifiableMap(currentActions);
Bundle extras = new Bundle();
- @Nullable ExoPlaybackException playbackError = player.getPlaybackError();
+ @Nullable ExoPlaybackException playbackError = player.getPlayerError();
boolean reportError = playbackError != null || customError != null;
int sessionPlaybackState =
reportError
@@ -780,10 +765,9 @@
queueNavigator != null
? queueNavigator.getActiveQueueItemId(player)
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
- PlaybackParameters playbackParameters = player.getPlaybackParameters();
- extras.putFloat(EXTRAS_SPEED, playbackParameters.speed);
- extras.putFloat(EXTRAS_PITCH, playbackParameters.pitch);
- float sessionPlaybackSpeed = player.isPlaying() ? playbackParameters.speed : 0f;
+ float playbackSpeed = player.getPlaybackSpeed();
+ extras.putFloat(EXTRAS_SPEED, playbackSpeed);
+ float sessionPlaybackSpeed = player.isPlaying() ? playbackSpeed : 0f;
builder
.setActions(buildPrepareActions() | buildPlaybackActions(player))
.setActiveQueueItemId(activeQueueItemId)
@@ -874,8 +858,8 @@
Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty() && !player.isPlayingAd()) {
enableSeeking = player.isCurrentWindowSeekable();
- enableRewind = enableSeeking && rewindMs > 0;
- enableFastForward = enableSeeking && fastForwardMs > 0;
+ enableRewind = enableSeeking && controlDispatcher.isRewindEnabled();
+ enableFastForward = enableSeeking && controlDispatcher.isFastForwardEnabled();
enableSetRating = ratingCallback != null;
enableSetCaptioningEnabled = captionCallback != null && captionCallback.hasCaptions(player);
}
@@ -954,28 +938,6 @@
return player != null && mediaButtonEventHandler != null;
}
- private void rewind(Player player) {
- if (player.isCurrentWindowSeekable() && rewindMs > 0) {
- seekToOffset(player, /* offsetMs= */ -rewindMs);
- }
- }
-
- private void fastForward(Player player) {
- if (player.isCurrentWindowSeekable() && fastForwardMs > 0) {
- seekToOffset(player, /* offsetMs= */ fastForwardMs);
- }
- }
-
- private void seekToOffset(Player player, long offsetMs) {
- long positionMs = player.getCurrentPosition() + offsetMs;
- long durationMs = player.getDuration();
- if (durationMs != C.TIME_UNSET) {
- positionMs = Math.min(positionMs, durationMs);
- }
- positionMs = Math.max(positionMs, 0);
- seekTo(player, player.getCurrentWindowIndex(), positionMs);
- }
-
private void seekTo(Player player, int windowIndex, long positionMs) {
controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs);
}
@@ -1126,7 +1088,13 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
+ invalidateMediaSessionPlaybackState();
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
invalidateMediaSessionPlaybackState();
}
@@ -1164,7 +1132,7 @@
}
@Override
- public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
invalidateMediaSessionPlaybackState();
}
@@ -1202,14 +1170,14 @@
@Override
public void onFastForward() {
if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
- fastForward(player);
+ controlDispatcher.dispatchFastForward(player);
}
}
@Override
public void onRewind() {
if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_REWIND)) {
- rewind(player);
+ controlDispatcher.dispatchRewind(player);
}
}
@@ -1291,7 +1259,7 @@
}
@Override
- public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+ public void onCommand(String command, @Nullable Bundle extras, @Nullable ResultReceiver cb) {
if (player != null) {
for (int i = 0; i < commandReceivers.size(); i++) {
if (commandReceivers.get(i).onCommand(player, controlDispatcher, command, extras, cb)) {
diff --git a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java
index 41bda3b..7f60d5e 100644
--- a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java
+++ b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java
@@ -191,9 +191,9 @@
Player player,
ControlDispatcher controlDispatcher,
String command,
- Bundle extras,
- ResultReceiver cb) {
- if (!COMMAND_MOVE_QUEUE_ITEM.equals(command)) {
+ @Nullable Bundle extras,
+ @Nullable ResultReceiver cb) {
+ if (!COMMAND_MOVE_QUEUE_ITEM.equals(command) || extras == null) {
return false;
}
int from = extras.getInt(EXTRA_FROM_INDEX, C.INDEX_UNSET);
diff --git a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
index fc4cc11..024faea 100644
--- a/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
+++ b/tree/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
@@ -36,7 +36,6 @@
*/
public abstract class TimelineQueueNavigator implements MediaSessionConnector.QueueNavigator {
- public static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
public static final int DEFAULT_MAX_QUEUE_SIZE = 10;
private final MediaSessionCompat mediaSession;
@@ -136,20 +135,7 @@
@Override
public void onSkipToPrevious(Player player, ControlDispatcher controlDispatcher) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- timeline.getWindow(windowIndex, window);
- int previousWindowIndex = player.getPreviousWindowIndex();
- if (previousWindowIndex != C.INDEX_UNSET
- && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
- || (window.isDynamic && !window.isSeekable))) {
- controlDispatcher.dispatchSeekTo(player, previousWindowIndex, C.TIME_UNSET);
- } else {
- controlDispatcher.dispatchSeekTo(player, windowIndex, 0);
- }
+ controlDispatcher.dispatchPrevious(player);
}
@Override
@@ -166,17 +152,7 @@
@Override
public void onSkipToNext(Player player, ControlDispatcher controlDispatcher) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- int nextWindowIndex = player.getNextWindowIndex();
- if (nextWindowIndex != C.INDEX_UNSET) {
- controlDispatcher.dispatchSeekTo(player, nextWindowIndex, C.TIME_UNSET);
- } else if (timeline.getWindow(windowIndex, window).isDynamic) {
- controlDispatcher.dispatchSeekTo(player, windowIndex, C.TIME_UNSET);
- }
+ controlDispatcher.dispatchNext(player);
}
// CommandReceiver implementation.
@@ -186,8 +162,8 @@
Player player,
ControlDispatcher controlDispatcher,
String command,
- Bundle extras,
- ResultReceiver cb) {
+ @Nullable Bundle extras,
+ @Nullable ResultReceiver cb) {
return false;
}
diff --git a/tree/extensions/okhttp/build.gradle b/tree/extensions/okhttp/build.gradle
index 2b4b485..b03abac 100644
--- a/tree/extensions/okhttp/build.gradle
+++ b/tree/extensions/okhttp/build.gradle
@@ -35,13 +35,14 @@
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
// Do not update to 3.13.X or later until minSdkVersion is increased to 21:
// https://cashapp.github.io/2019-02-05/okhttp-3-13-requires-android-5
// Since OkHttp is distributed as a jar rather than an aar, Gradle won't
// stop us from making this mistake!
- api 'com.squareup.okhttp3:okhttp:3.12.7'
+ api 'com.squareup.okhttp3:okhttp:3.12.8'
}
ext {
diff --git a/tree/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/tree/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
index 3053961..fe2bdd6 100644
--- a/tree/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
+++ b/tree/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
@@ -223,7 +223,7 @@
responseByteStream = responseBody.byteStream();
} catch (IOException e) {
throw new HttpDataSourceException(
- "Unable to connect to " + dataSpec.uri, e, dataSpec, HttpDataSourceException.TYPE_OPEN);
+ "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
int responseCode = response.code();
diff --git a/tree/extensions/okhttp/src/test/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceTest.java b/tree/extensions/okhttp/src/test/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceTest.java
index dab62b0..393c048 100644
--- a/tree/extensions/okhttp/src/test/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceTest.java
+++ b/tree/extensions/okhttp/src/test/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
@@ -86,16 +85,12 @@
dataSpecRequestProperties.put("5", dataSpecValue);
DataSpec dataSpec =
- new DataSpec(
- /* uri= */ Uri.parse("http://www.google.com"),
- /* httpMethod= */ 1,
- /* httpBody= */ null,
- /* absoluteStreamPosition= */ 1000,
- /* position= */ 1000,
- /* length= */ 5000,
- /* key= */ null,
- /* flags= */ 0,
- dataSpecRequestProperties);
+ new DataSpec.Builder()
+ .setUri("http://www.google.com")
+ .setPosition(1000)
+ .setLength(5000)
+ .setHttpRequestHeaders(dataSpecRequestProperties)
+ .build();
Mockito.doAnswer(
invocation -> {
diff --git a/tree/extensions/opus/build.gradle b/tree/extensions/opus/build.gradle
index 28cf8f1..545b5a7 100644
--- a/tree/extensions/opus/build.gradle
+++ b/tree/extensions/opus/build.gradle
@@ -29,9 +29,12 @@
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
- sourceSets.main {
- jniLibs.srcDir 'src/main/libs'
- jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ sourceSets {
+ main {
+ jniLibs.srcDir 'src/main/libs'
+ jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ }
+ androidTest.assets.srcDir '../../testdata/src/test/assets/'
}
testOptions.unitTests.includeAndroidResources = true
@@ -40,6 +43,7 @@
dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
diff --git a/tree/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/tree/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java
index 3b5239e..e4e392f 100644
--- a/tree/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java
+++ b/tree/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.net.Uri;
import android.os.Looper;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -37,7 +38,9 @@
@RunWith(AndroidJUnit4.class)
public class OpusPlaybackTest {
- private static final String BEAR_OPUS_URI = "asset:///bear-opus.webm";
+ private static final String BEAR_OPUS_URI = "asset:///mka/bear-opus.mka";
+ private static final String BEAR_OPUS_NEGATIVE_GAIN_URI =
+ "asset:///mka/bear-opus-negative-gain.mka";
@Before
public void setUp() {
@@ -47,10 +50,15 @@
}
@Test
- public void testBasicPlayback() throws Exception {
+ public void basicPlayback() throws Exception {
playUri(BEAR_OPUS_URI);
}
+ @Test
+ public void basicPlaybackNegativeGain() throws Exception {
+ playUri(BEAR_OPUS_NEGATIVE_GAIN_URI);
+ }
+
private void playUri(String uri) throws Exception {
TestPlaybackRunnable testPlaybackRunnable =
new TestPlaybackRunnable(Uri.parse(uri), ApplicationProvider.getApplicationContext());
@@ -67,8 +75,8 @@
private final Context context;
private final Uri uri;
- private ExoPlayer player;
- private ExoPlaybackException playbackException;
+ @Nullable private ExoPlayer player;
+ @Nullable private ExoPlaybackException playbackException;
public TestPlaybackRunnable(Uri uri, Context context) {
this.uri = uri;
@@ -97,7 +105,7 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
player.release();
diff --git a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java
index 3592331..3917214 100644
--- a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java
+++ b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java
@@ -21,15 +21,15 @@
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
-import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.audio.DecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.TraceUtil;
/** Decodes and renders audio using the native Opus decoder. */
-public class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
+public class LibopusAudioRenderer extends DecoderAudioRenderer {
+ private static final String TAG = "LibopusAudioRenderer";
/** The number of input and output buffers. */
private static final int NUM_BUFFERS = 16;
/** The default input buffer size. */
@@ -55,42 +55,17 @@
super(eventHandler, eventListener, audioProcessors);
}
- /**
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
- * @deprecated Use {@link #LibopusAudioRenderer(Handler, AudioRendererEventListener,
- * AudioProcessor...)} instead, and pass DRM-related parameters to the {@link MediaSource}
- * factories.
- */
- @Deprecated
- public LibopusAudioRenderer(
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- AudioProcessor... audioProcessors) {
- super(eventHandler, eventListener, null, drmSessionManager, playClearSamplesWithoutKeys,
- audioProcessors);
+ @Override
+ public String getName() {
+ return TAG;
}
@Override
@FormatSupport
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
+ protected int supportsFormatInternal(Format format) {
boolean drmIsSupported =
format.drmInitData == null
- || OpusLibrary.matchesExpectedExoMediaCryptoType(format.exoMediaCryptoType)
- || (format.exoMediaCryptoType == null
- && supportsFormatDrm(drmSessionManager, format.drmInitData));
+ || OpusLibrary.matchesExpectedExoMediaCryptoType(format.exoMediaCryptoType);
if (!OpusLibrary.isAvailable()
|| !MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
@@ -106,6 +81,7 @@
@Override
protected OpusDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws OpusDecoderException {
+ TraceUtil.beginSection("createOpusDecoder");
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
OpusDecoder decoder =
@@ -117,23 +93,17 @@
mediaCrypto);
channelCount = decoder.getChannelCount();
sampleRate = decoder.getSampleRate();
+ TraceUtil.endSection();
return decoder;
}
@Override
protected Format getOutputFormat() {
- return Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- sampleRate,
- C.ENCODING_PCM_16BIT,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setPcmEncoding(C.ENCODING_PCM_16BIT)
+ .build();
}
}
diff --git a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java
index f0e993e..8795950 100644
--- a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java
+++ b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java
@@ -90,8 +90,8 @@
if (channelCount > 8) {
throw new OpusDecoderException("Invalid channel count: " + channelCount);
}
- int preskip = readLittleEndian16(headerBytes, 10);
- int gain = readLittleEndian16(headerBytes, 16);
+ int preskip = readUnsignedLittleEndian16(headerBytes, 10);
+ int gain = readSignedLittleEndian16(headerBytes, 16);
byte[] streamMap = new byte[8];
int numStreams;
@@ -148,7 +148,7 @@
@Override
protected SimpleOutputBuffer createOutputBuffer() {
- return new SimpleOutputBuffer(this);
+ return new SimpleOutputBuffer(this::releaseOutputBuffer);
}
@Override
@@ -228,12 +228,16 @@
return (int) (ns * SAMPLE_RATE / 1000000000);
}
- private static int readLittleEndian16(byte[] input, int offset) {
+ private static int readUnsignedLittleEndian16(byte[] input, int offset) {
int value = input[offset] & 0xFF;
value |= (input[offset + 1] & 0xFF) << 8;
return value;
}
+ private static int readSignedLittleEndian16(byte[] input, int offset) {
+ return (short) readUnsignedLittleEndian16(input, offset);
+ }
+
private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled,
int gain, byte[] streamMap);
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize,
diff --git a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoderException.java b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoderException.java
index 6645086..8d9cfea 100644
--- a/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoderException.java
+++ b/tree/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoderException.java
@@ -15,12 +15,10 @@
*/
package com.google.android.exoplayer2.ext.opus;
-import com.google.android.exoplayer2.audio.AudioDecoderException;
+import com.google.android.exoplayer2.decoder.DecoderException;
-/**
- * Thrown when an Opus decoder error occurs.
- */
-public final class OpusDecoderException extends AudioDecoderException {
+/** Thrown when an Opus decoder error occurs. */
+public final class OpusDecoderException extends DecoderException {
/* package */ OpusDecoderException(String message) {
super(message);
diff --git a/tree/extensions/rtmp/build.gradle b/tree/extensions/rtmp/build.gradle
index 88d3524..621f8b2 100644
--- a/tree/extensions/rtmp/build.gradle
+++ b/tree/extensions/rtmp/build.gradle
@@ -34,6 +34,7 @@
implementation project(modulePrefix + 'library-core')
implementation 'net.butterflytv.utils:rtmp-client:3.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/extensions/vp9/build.gradle b/tree/extensions/vp9/build.gradle
index 80239be..ffd76d6 100644
--- a/tree/extensions/vp9/build.gradle
+++ b/tree/extensions/vp9/build.gradle
@@ -29,9 +29,12 @@
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
- sourceSets.main {
- jniLibs.srcDir 'src/main/libs'
- jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ sourceSets {
+ main {
+ jniLibs.srcDir 'src/main/libs'
+ jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
+ }
+ androidTest.assets.srcDir '../../testdata/src/test/assets/'
}
testOptions.unitTests.includeAndroidResources = true
@@ -40,6 +43,7 @@
dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
diff --git a/tree/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/tree/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java
index ed8bd00..7b81c0b 100644
--- a/tree/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java
+++ b/tree/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.net.Uri;
import android.os.Looper;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
@@ -41,10 +42,10 @@
@RunWith(AndroidJUnit4.class)
public class VpxPlaybackTest {
- private static final String BEAR_URI = "asset:///bear-vp9.webm";
- private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///bear-vp9-odd-dimensions.webm";
- private static final String ROADTRIP_10BIT_URI = "asset:///roadtrip-vp92-10bit.webm";
- private static final String INVALID_BITSTREAM_URI = "asset:///invalid-bitstream.webm";
+ private static final String BEAR_URI = "asset:///vp9/bear-vp9.webm";
+ private static final String BEAR_ODD_DIMENSIONS_URI = "asset:///vp9/bear-vp9-odd-dimensions.webm";
+ private static final String ROADTRIP_10BIT_URI = "asset:///vp9/roadtrip-vp92-10bit.webm";
+ private static final String INVALID_BITSTREAM_URI = "asset:///vp9/invalid-bitstream.webm";
private static final String TAG = "VpxPlaybackTest";
@@ -56,12 +57,12 @@
}
@Test
- public void testBasicPlayback() throws Exception {
+ public void basicPlayback() throws Exception {
playUri(BEAR_URI);
}
@Test
- public void testOddDimensionsPlayback() throws Exception {
+ public void oddDimensionsPlayback() throws Exception {
playUri(BEAR_ODD_DIMENSIONS_URI);
}
@@ -76,7 +77,7 @@
}
@Test
- public void testInvalidBitstream() {
+ public void invalidBitstream() {
try {
playUri(INVALID_BITSTREAM_URI);
fail();
@@ -102,8 +103,8 @@
private final Context context;
private final Uri uri;
- private ExoPlayer player;
- private ExoPlaybackException playbackException;
+ @Nullable private ExoPlayer player;
+ @Nullable private ExoPlaybackException playbackException;
public TestPlaybackRunnable(Uri uri, Context context) {
this.uri = uri;
@@ -137,7 +138,7 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
player.release();
diff --git a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
index 07d532f..d516780 100644
--- a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
+++ b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
@@ -21,40 +21,19 @@
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
-import com.google.android.exoplayer2.video.SimpleDecoderVideoRenderer;
-import com.google.android.exoplayer2.video.VideoDecoderException;
-import com.google.android.exoplayer2.video.VideoDecoderInputBuffer;
+import com.google.android.exoplayer2.video.DecoderVideoRenderer;
import com.google.android.exoplayer2.video.VideoDecoderOutputBuffer;
-import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
-import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
-/**
- * Decodes and renders video using the native VP9 decoder.
- *
- * <p>This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)}
- * on the playback thread:
- *
- * <ul>
- * <li>Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload
- * should be the target {@link Surface}, or null.
- * <li>Message with type {@link #MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER} to set the output
- * buffer renderer. The message payload should be the target {@link
- * VideoDecoderOutputBufferRenderer}, or null.
- * </ul>
- */
-public class LibvpxVideoRenderer extends SimpleDecoderVideoRenderer {
+/** Decodes and renders video using the native VP9 decoder. */
+public class LibvpxVideoRenderer extends DecoderVideoRenderer {
+
+ private static final String TAG = "LibvpxVideoRenderer";
/** The number of input buffers. */
private final int numInputBuffers;
@@ -72,9 +51,10 @@
private final int threads;
@Nullable private VpxDecoder decoder;
- @Nullable private VideoFrameMetadataListener frameMetadataListener;
/**
+ * Creates a new instance.
+ *
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
*/
@@ -83,6 +63,8 @@
}
/**
+ * Creates a new instance.
+ *
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
@@ -101,51 +83,14 @@
eventHandler,
eventListener,
maxDroppedFramesToNotify,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false);
- }
-
- /**
- * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
- * can attempt to seamlessly join an ongoing playback.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
- * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @deprecated Use {@link #LibvpxVideoRenderer(long, Handler, VideoRendererEventListener, int,
- * int, int, int)}} instead, and pass DRM-related parameters to the {@link MediaSource}
- * factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public LibvpxVideoRenderer(
- long allowedJoiningTimeMs,
- @Nullable Handler eventHandler,
- @Nullable VideoRendererEventListener eventListener,
- int maxDroppedFramesToNotify,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys) {
- this(
- allowedJoiningTimeMs,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify,
- drmSessionManager,
- playClearSamplesWithoutKeys,
getRuntime().availableProcessors(),
/* numInputBuffers= */ 4,
/* numOutputBuffers= */ 4);
}
/**
+ * Creates a new instance.
+ *
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
@@ -166,75 +111,26 @@
int threads,
int numInputBuffers,
int numOutputBuffers) {
- this(
- allowedJoiningTimeMs,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- threads,
- numInputBuffers,
- numOutputBuffers);
- }
-
- /**
- * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
- * can attempt to seamlessly join an ongoing playback.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
- * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param threads Number of threads libvpx will use to decode.
- * @param numInputBuffers Number of input buffers.
- * @param numOutputBuffers Number of output buffers.
- * @deprecated Use {@link #LibvpxVideoRenderer(long, Handler, VideoRendererEventListener, int,
- * int, int, int)}} instead, and pass DRM-related parameters to the {@link MediaSource}
- * factories.
- */
- @Deprecated
- public LibvpxVideoRenderer(
- long allowedJoiningTimeMs,
- @Nullable Handler eventHandler,
- @Nullable VideoRendererEventListener eventListener,
- int maxDroppedFramesToNotify,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- int threads,
- int numInputBuffers,
- int numOutputBuffers) {
- super(
- allowedJoiningTimeMs,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify,
- drmSessionManager,
- playClearSamplesWithoutKeys);
+ super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
this.threads = threads;
this.numInputBuffers = numInputBuffers;
this.numOutputBuffers = numOutputBuffers;
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
+ public final int supportsFormat(Format format) {
if (!VpxLibrary.isAvailable() || !MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
}
boolean drmIsSupported =
format.drmInitData == null
- || VpxLibrary.matchesExpectedExoMediaCryptoType(format.exoMediaCryptoType)
- || (format.exoMediaCryptoType == null
- && supportsFormatDrm(drmSessionManager, format.drmInitData));
+ || VpxLibrary.matchesExpectedExoMediaCryptoType(format.exoMediaCryptoType);
if (!drmIsSupported) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM);
}
@@ -242,12 +138,8 @@
}
@Override
- protected SimpleDecoder<
- VideoDecoderInputBuffer,
- ? extends VideoDecoderOutputBuffer,
- ? extends VideoDecoderException>
- createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
- throws VideoDecoderException {
+ protected VpxDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
+ throws VpxDecoderException {
TraceUtil.beginSection("createVpxDecoder");
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
@@ -260,17 +152,6 @@
}
@Override
- protected void renderOutputBuffer(
- VideoDecoderOutputBuffer outputBuffer, long presentationTimeUs, Format outputFormat)
- throws VideoDecoderException {
- if (frameMetadataListener != null) {
- frameMetadataListener.onVideoFrameAboutToBeRendered(
- presentationTimeUs, System.nanoTime(), outputFormat, /* mediaFormat= */ null);
- }
- super.renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
- }
-
- @Override
protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
throws VpxDecoderException {
if (decoder == null) {
@@ -287,19 +168,4 @@
decoder.setOutputMode(outputMode);
}
}
-
- // PlayerMessage.Target implementation.
-
- @Override
- public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
- if (messageType == MSG_SET_SURFACE) {
- setOutputSurface((Surface) message);
- } else if (messageType == MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) {
- setOutputBufferRenderer((VideoDecoderOutputBufferRenderer) message);
- } else if (messageType == MSG_SET_VIDEO_FRAME_METADATA_LISTENER) {
- frameMetadataListener = (VideoFrameMetadataListener) message;
- } else {
- super.handleMessage(messageType, message);
- }
- }
}
diff --git a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoderException.java b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoderException.java
index b2da9a7..686790b 100644
--- a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoderException.java
+++ b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxDecoderException.java
@@ -15,10 +15,10 @@
*/
package com.google.android.exoplayer2.ext.vp9;
-import com.google.android.exoplayer2.video.VideoDecoderException;
+import com.google.android.exoplayer2.decoder.DecoderException;
/** Thrown when a libvpx decoder error occurs. */
-public final class VpxDecoderException extends VideoDecoderException {
+public final class VpxDecoderException extends DecoderException {
/* package */ VpxDecoderException(String message) {
super(message);
diff --git a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java
index 1c43403..99f3521 100644
--- a/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java
+++ b/tree/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java
@@ -32,7 +32,7 @@
*
* @param owner Buffer owner.
*/
- public VpxOutputBuffer(VideoDecoderOutputBuffer.Owner owner) {
+ public VpxOutputBuffer(Owner<VideoDecoderOutputBuffer> owner) {
super(owner);
}
}
diff --git a/tree/extensions/vp9/src/main/jni/vpx_jni.cc b/tree/extensions/vp9/src/main/jni/vpx_jni.cc
index 823f9b8..9996848 100644
--- a/tree/extensions/vp9/src/main/jni/vpx_jni.cc
+++ b/tree/extensions/vp9/src/main/jni/vpx_jni.cc
@@ -535,7 +535,7 @@
// LINT.IfChange
const int kOutputModeYuv = 0;
const int kOutputModeSurfaceYuv = 1;
- // LINT.ThenChange(../../../../../library/core/src/main/java/com/google/android/exoplayer2/C.java)
+ // LINT.ThenChange(../../../../../library/common/src/main/java/com/google/android/exoplayer2/C.java)
int outputMode = env->GetIntField(jOutputBuffer, outputModeField);
if (outputMode == kOutputModeYuv) {
diff --git a/tree/extensions/workmanager/build.gradle b/tree/extensions/workmanager/build.gradle
index 6a7aa10..6025ecf 100644
--- a/tree/extensions/workmanager/build.gradle
+++ b/tree/extensions/workmanager/build.gradle
@@ -34,7 +34,8 @@
dependencies {
implementation project(modulePrefix + 'library-core')
- implementation 'androidx.work:work-runtime:2.2.0'
+ implementation 'androidx.work:work-runtime:2.3.4'
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
}
ext {
diff --git a/tree/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java b/tree/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java
index 01801c9..97b1329 100644
--- a/tree/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java
+++ b/tree/extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java
@@ -15,9 +15,9 @@
*/
package com.google.android.exoplayer2.ext.workmanager;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
+import androidx.annotation.RequiresApi;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
@@ -92,7 +92,7 @@
return builder.build();
}
- @TargetApi(23)
+ @RequiresApi(23)
private static void setRequiresDeviceIdle(Constraints.Builder builder) {
builder.setRequiresDeviceIdle(true);
}
diff --git a/tree/gradle.properties b/tree/gradle.properties
new file mode 100644
index 0000000..297016a
--- /dev/null
+++ b/tree/gradle.properties
@@ -0,0 +1,5 @@
+## Project-wide Gradle settings.
+android.useAndroidX=true
+android.enableJetifier=true
+buildDir=buildout
+org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
diff --git a/tree/gradle/wrapper/gradle-wrapper.properties b/tree/gradle/wrapper/gradle-wrapper.properties
index 7fefd1c..02d1a89 100644
--- a/tree/gradle/wrapper/gradle-wrapper.properties
+++ b/tree/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Oct 07 17:24:00 BST 2019
+#Wed Mar 04 12:41:50 GMT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/tree/javadoc_combined.gradle b/tree/javadoc_combined.gradle
index d2fa241..3b48291 100644
--- a/tree/javadoc_combined.gradle
+++ b/tree/javadoc_combined.gradle
@@ -29,8 +29,7 @@
classpath = project.files([])
destinationDir = project.file("$project.buildDir/docs/javadoc")
options {
- links "https://docs.oracle.com/javase/7/docs/api/",
- "https://developer.android.com/reference"
+ links "https://developer.android.com/reference"
encoding = "UTF-8"
}
exclude "**/BuildConfig.java"
diff --git a/tree/javadoc_library.gradle b/tree/javadoc_library.gradle
index 74fcc3d..dd508a1 100644
--- a/tree/javadoc_library.gradle
+++ b/tree/javadoc_library.gradle
@@ -26,9 +26,7 @@
title = "ExoPlayer ${javadocTitle}"
source = allSourceDirs
options {
- links "http://docs.oracle.com/javase/7/docs/api/"
- linksOffline "https://developer.android.com/reference",
- "${android.sdkDirectory}/docs/reference"
+ links "https://developer.android.com/reference"
encoding = "UTF-8"
}
exclude "**/BuildConfig.java"
diff --git a/tree/javadoc_util.gradle b/tree/javadoc_util.gradle
index d5b1f56..b9962d3 100644
--- a/tree/javadoc_util.gradle
+++ b/tree/javadoc_util.gradle
@@ -15,18 +15,11 @@
def javadocPath = "${project.buildDir}/docs/javadoc"
// Fix external Android links to target the top frame.
def androidRoot = "https://developer.android.com/reference/"
- def androidLink = "<a href=\"(${androidRoot}.*?)\\?is-external=true\""
- def androidFixed = "<a href=\"\\1\" target=\"_top\""
+ def androidLink = "<a href=\"(${androidRoot}.*?)\\?is-external=true(.*)\""
+ def androidFixed = "<a href=\"\\1\\2\" target=\"_top\""
ant.replaceregexp(match:androidLink, replace:androidFixed, flags:'g') {
fileset(dir: "${javadocPath}", includes: "**/*.html")
}
- // Fix external Oracle links to use frames and target the top frame.
- def oracleRoot = "https://docs.oracle.com/javase/7/docs/api/"
- def oracleLink = "<a href=\"(${oracleRoot})(.*?)\\?is-external=true\""
- def oracleFixed = "<a href=\"\\1index.html\\?\\2\" target=\"_top\""
- ant.replaceregexp(match:oracleLink, replace:oracleFixed, flags:'g') {
- fileset(dir: "${javadocPath}", includes: "**/*.html")
- }
// Add favicon to each page
def headTag = "<head>"
def headTagWithFavicon = "<head>" +
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
index af23854..780d825 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2;
-import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
@@ -23,6 +22,7 @@
import android.media.MediaCodec;
import android.media.MediaFormat;
import androidx.annotation.IntDef;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -160,6 +160,11 @@
ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT,
ENCODING_MP3,
+ ENCODING_AAC_LC,
+ ENCODING_AAC_HE_V1,
+ ENCODING_AAC_HE_V2,
+ ENCODING_AAC_XHE,
+ ENCODING_AAC_ELD,
ENCODING_AC3,
ENCODING_E_AC3,
ENCODING_E_AC3_JOC,
@@ -205,6 +210,16 @@
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
/** @see AudioFormat#ENCODING_MP3 */
public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
+ /** @see AudioFormat#ENCODING_AAC_LC */
+ public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC;
+ /** @see AudioFormat#ENCODING_AAC_HE_V1 */
+ public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1;
+ /** @see AudioFormat#ENCODING_AAC_HE_V2 */
+ public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2;
+ /** @see AudioFormat#ENCODING_AAC_XHE */
+ public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE;
+ /** @see AudioFormat#ENCODING_AAC_ELD */
+ public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD;
/** @see AudioFormat#ENCODING_AC3 */
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/** @see AudioFormat#ENCODING_E_AC3 */
@@ -947,6 +962,37 @@
public static final int NETWORK_TYPE_OTHER = 8;
/**
+ * Mode specifying whether the player should hold a WakeLock and a WifiLock. One of {@link
+ * #WAKE_MODE_NONE}, {@link #WAKE_MODE_LOCAL} and {@link #WAKE_MODE_NETWORK}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({WAKE_MODE_NONE, WAKE_MODE_LOCAL, WAKE_MODE_NETWORK})
+ public @interface WakeMode {}
+ /**
+ * A wake mode that will not cause the player to hold any locks.
+ *
+ * <p>This is suitable for applications that do not play media with the screen off.
+ */
+ public static final int WAKE_MODE_NONE = 0;
+ /**
+ * A wake mode that will cause the player to hold a {@link android.os.PowerManager.WakeLock}
+ * during playback.
+ *
+ * <p>This is suitable for applications that play media with the screen off and do not load media
+ * over wifi.
+ */
+ public static final int WAKE_MODE_LOCAL = 1;
+ /**
+ * A wake mode that will cause the player to hold a {@link android.os.PowerManager.WakeLock} and a
+ * {@link android.net.wifi.WifiManager.WifiLock} during playback.
+ *
+ * <p>This is suitable for applications that play media with the screen off and may load media
+ * over wifi.
+ */
+ public static final int WAKE_MODE_NETWORK = 2;
+
+ /**
* Track role flags. Possible flag values are {@link #ROLE_FLAG_MAIN}, {@link
* #ROLE_FLAG_ALTERNATE}, {@link #ROLE_FLAG_SUPPLEMENTARY}, {@link #ROLE_FLAG_COMMENTARY}, {@link
* #ROLE_FLAG_DUB}, {@link #ROLE_FLAG_EMERGENCY}, {@link #ROLE_FLAG_CAPTION}, {@link
@@ -1047,7 +1093,7 @@
*
* @see AudioManager#generateAudioSessionId()
*/
- @TargetApi(21)
+ @RequiresApi(21)
public static int generateAudioSessionIdV21(Context context) {
return ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE))
.generateAudioSessionId();
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
index d6760a3..0674373 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
@@ -29,11 +29,11 @@
/** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
- public static final String VERSION = "2.11.2";
+ public static final String VERSION = "2.11.4";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
- public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.2";
+ public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.4";
/**
* The version of the library expressed as an integer, for example 1002003.
@@ -43,7 +43,7 @@
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
- public static final int VERSION_INT = 2011002;
+ public static final int VERSION_INT = 2011004;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/Format.java
index 19ed344..e7db47d 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/Format.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/Format.java
@@ -19,7 +19,6 @@
import android.os.Parcelable;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.drm.DrmInitData;
-import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes;
@@ -31,13 +30,601 @@
import java.util.List;
/**
- * Representation of a media format.
+ * Represents a media format.
+ *
+ * <p>When building formats, populate all fields whose values are known and relevant to the type of
+ * format being constructed. For information about different types of format, see ExoPlayer's <a
+ * href="https://exoplayer.dev/supported-formats.html">Supported formats page</a>.
+ *
+ * <h3>Fields commonly relevant to all formats</h3>
+ *
+ * <ul>
+ * <li>{@link #id}
+ * <li>{@link #label}
+ * <li>{@link #language}
+ * <li>{@link #selectionFlags}
+ * <li>{@link #roleFlags}
+ * <li>{@link #averageBitrate}
+ * <li>{@link #peakBitrate}
+ * <li>{@link #codecs}
+ * <li>{@link #metadata}
+ * </ul>
+ *
+ * <h3 id="container-formats">Fields relevant to container formats</h3>
+ *
+ * <ul>
+ * <li>{@link #containerMimeType}
+ * <li>If the container only contains a single media track, <a href="#sample-formats">fields
+ * relevant to sample formats</a> can are also be relevant and can be set to describe the
+ * sample format of that track.
+ * <li>If the container only contains one track of a given type (possibly alongside tracks of
+ * other types), then fields relevant to that track type can be set to describe the properties
+ * of the track. See the sections below for <a href="#video-formats">video</a>, <a
+ * href="#audio-formats">audio</a> and <a href="#text-formats">text</a> formats.
+ * </ul>
+ *
+ * <h3 id="sample-formats">Fields relevant to sample formats</h3>
+ *
+ * <ul>
+ * <li>{@link #sampleMimeType}
+ * <li>{@link #maxInputSize}
+ * <li>{@link #initializationData}
+ * <li>{@link #drmInitData}
+ * <li>{@link #subsampleOffsetUs}
+ * <li>Fields relevant to the sample format's track type are also relevant. See the sections below
+ * for <a href="#video-formats">video</a>, <a href="#audio-formats">audio</a> and <a
+ * href="#text-formats">text</a> formats.
+ * </ul>
+ *
+ * <h3 id="video-formats">Fields relevant to video formats</h3>
+ *
+ * <ul>
+ * <li>{@link #width}
+ * <li>{@link #height}
+ * <li>{@link #frameRate}
+ * <li>{@link #rotationDegrees}
+ * <li>{@link #pixelWidthHeightRatio}
+ * <li>{@link #projectionData}
+ * <li>{@link #stereoMode}
+ * <li>{@link #colorInfo}
+ * </ul>
+ *
+ * <h3 id="audio-formats">Fields relevant to audio formats</h3>
+ *
+ * <ul>
+ * <li>{@link #channelCount}
+ * <li>{@link #sampleRate}
+ * <li>{@link #pcmEncoding}
+ * <li>{@link #encoderDelay}
+ * <li>{@link #encoderPadding}
+ * </ul>
+ *
+ * <h3 id="text-formats">Fields relevant to text formats</h3>
+ *
+ * <ul>
+ * <li>{@link #accessibilityChannel}
+ * </ul>
*/
public final class Format implements Parcelable {
/**
- * A value for various fields to indicate that the field's value is unknown or not applicable.
+ * Builds {@link Format} instances.
+ *
+ * <p>Use Format#buildUpon() to obtain a builder representing an existing {@link Format}.
+ *
+ * <p>When building formats, populate all fields whose values are known and relevant to the type
+ * of format being constructed. See the {@link Format} Javadoc for information about which fields
+ * should be set for different types of format.
*/
+ public static final class Builder {
+
+ @Nullable private String id;
+ @Nullable private String label;
+ @Nullable private String language;
+ @C.SelectionFlags private int selectionFlags;
+ @C.RoleFlags private int roleFlags;
+ private int averageBitrate;
+ private int peakBitrate;
+ @Nullable private String codecs;
+ @Nullable private Metadata metadata;
+
+ // Container specific.
+
+ @Nullable private String containerMimeType;
+
+ // Sample specific.
+
+ @Nullable private String sampleMimeType;
+ private int maxInputSize;
+ @Nullable private List<byte[]> initializationData;
+ @Nullable private DrmInitData drmInitData;
+ private long subsampleOffsetUs;
+
+ // Video specific.
+
+ private int width;
+ private int height;
+ private float frameRate;
+ private int rotationDegrees;
+ private float pixelWidthHeightRatio;
+ @Nullable private byte[] projectionData;
+ @C.StereoMode private int stereoMode;
+ @Nullable private ColorInfo colorInfo;
+
+ // Audio specific.
+
+ private int channelCount;
+ private int sampleRate;
+ @C.PcmEncoding private int pcmEncoding;
+ private int encoderDelay;
+ private int encoderPadding;
+
+ // Text specific.
+
+ private int accessibilityChannel;
+
+ // Provided by source.
+
+ @Nullable private Class<? extends ExoMediaCrypto> exoMediaCryptoType;
+
+ /** Creates a new instance with default values. */
+ public Builder() {
+ averageBitrate = NO_VALUE;
+ peakBitrate = NO_VALUE;
+ // Sample specific.
+ maxInputSize = NO_VALUE;
+ subsampleOffsetUs = OFFSET_SAMPLE_RELATIVE;
+ // Video specific.
+ width = NO_VALUE;
+ height = NO_VALUE;
+ frameRate = NO_VALUE;
+ pixelWidthHeightRatio = 1.0f;
+ stereoMode = NO_VALUE;
+ // Audio specific.
+ channelCount = NO_VALUE;
+ sampleRate = NO_VALUE;
+ pcmEncoding = NO_VALUE;
+ // Text specific.
+ accessibilityChannel = NO_VALUE;
+ }
+
+ /**
+ * Creates a new instance to build upon the provided {@link Format}.
+ *
+ * @param format The {@link Format} to build upon.
+ */
+ private Builder(Format format) {
+ this.id = format.id;
+ this.label = format.label;
+ this.language = format.language;
+ this.selectionFlags = format.selectionFlags;
+ this.roleFlags = format.roleFlags;
+ this.averageBitrate = format.averageBitrate;
+ this.peakBitrate = format.peakBitrate;
+ this.codecs = format.codecs;
+ this.metadata = format.metadata;
+ // Container specific.
+ this.containerMimeType = format.containerMimeType;
+ // Sample specific.
+ this.sampleMimeType = format.sampleMimeType;
+ this.maxInputSize = format.maxInputSize;
+ this.initializationData = format.initializationData;
+ this.drmInitData = format.drmInitData;
+ this.subsampleOffsetUs = format.subsampleOffsetUs;
+ // Video specific.
+ this.width = format.width;
+ this.height = format.height;
+ this.frameRate = format.frameRate;
+ this.rotationDegrees = format.rotationDegrees;
+ this.pixelWidthHeightRatio = format.pixelWidthHeightRatio;
+ this.projectionData = format.projectionData;
+ this.stereoMode = format.stereoMode;
+ this.colorInfo = format.colorInfo;
+ // Audio specific.
+ this.channelCount = format.channelCount;
+ this.sampleRate = format.sampleRate;
+ this.pcmEncoding = format.pcmEncoding;
+ this.encoderDelay = format.encoderDelay;
+ this.encoderPadding = format.encoderPadding;
+ // Text specific.
+ this.accessibilityChannel = format.accessibilityChannel;
+ // Provided by source.
+ this.exoMediaCryptoType = format.exoMediaCryptoType;
+ }
+
+ /**
+ * Sets {@link Format#id}. The default value is {@code null}.
+ *
+ * @param id The {@link Format#id}.
+ * @return The builder.
+ */
+ public Builder setId(@Nullable String id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#id} to {@link Integer#toString() Integer.toString(id)}. The default value
+ * is {@code null}.
+ *
+ * @param id The {@link Format#id}.
+ * @return The builder.
+ */
+ public Builder setId(int id) {
+ this.id = Integer.toString(id);
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#label}. The default value is {@code null}.
+ *
+ * @param label The {@link Format#label}.
+ * @return The builder.
+ */
+ public Builder setLabel(@Nullable String label) {
+ this.label = label;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#language}. The default value is {@code null}.
+ *
+ * @param language The {@link Format#language}.
+ * @return The builder.
+ */
+ public Builder setLanguage(@Nullable String language) {
+ this.language = language;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#selectionFlags}. The default value is 0.
+ *
+ * @param selectionFlags The {@link Format#selectionFlags}.
+ * @return The builder.
+ */
+ public Builder setSelectionFlags(@C.SelectionFlags int selectionFlags) {
+ this.selectionFlags = selectionFlags;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#roleFlags}. The default value is 0.
+ *
+ * @param roleFlags The {@link Format#roleFlags}.
+ * @return The builder.
+ */
+ public Builder setRoleFlags(@C.RoleFlags int roleFlags) {
+ this.roleFlags = roleFlags;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#averageBitrate}. The default value is {@link #NO_VALUE}.
+ *
+ * @param averageBitrate The {@link Format#averageBitrate}.
+ * @return The builder.
+ */
+ public Builder setAverageBitrate(int averageBitrate) {
+ this.averageBitrate = averageBitrate;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#peakBitrate}. The default value is {@link #NO_VALUE}.
+ *
+ * @param peakBitrate The {@link Format#peakBitrate}.
+ * @return The builder.
+ */
+ public Builder setPeakBitrate(int peakBitrate) {
+ this.peakBitrate = peakBitrate;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#codecs}. The default value is {@code null}.
+ *
+ * @param codecs The {@link Format#codecs}.
+ * @return The builder.
+ */
+ public Builder setCodecs(@Nullable String codecs) {
+ this.codecs = codecs;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#metadata}. The default value is {@code null}.
+ *
+ * @param metadata The {@link Format#metadata}.
+ * @return The builder.
+ */
+ public Builder setMetadata(@Nullable Metadata metadata) {
+ this.metadata = metadata;
+ return this;
+ }
+
+ // Container specific.
+
+ /**
+ * Sets {@link Format#containerMimeType}. The default value is {@code null}.
+ *
+ * @param containerMimeType The {@link Format#containerMimeType}.
+ * @return The builder.
+ */
+ public Builder setContainerMimeType(@Nullable String containerMimeType) {
+ this.containerMimeType = containerMimeType;
+ return this;
+ }
+
+ // Sample specific.
+
+ /**
+ * Sets {@link Format#sampleMimeType}. The default value is {@code null}.
+ *
+ * @param sampleMimeType {@link Format#sampleMimeType}.
+ * @return The builder.
+ */
+ public Builder setSampleMimeType(@Nullable String sampleMimeType) {
+ this.sampleMimeType = sampleMimeType;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#maxInputSize}. The default value is {@link #NO_VALUE}.
+ *
+ * @param maxInputSize The {@link Format#maxInputSize}.
+ * @return The builder.
+ */
+ public Builder setMaxInputSize(int maxInputSize) {
+ this.maxInputSize = maxInputSize;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#initializationData}. The default value is {@code null}.
+ *
+ * @param initializationData The {@link Format#initializationData}.
+ * @return The builder.
+ */
+ public Builder setInitializationData(@Nullable List<byte[]> initializationData) {
+ this.initializationData = initializationData;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#drmInitData}. The default value is {@code null}.
+ *
+ * @param drmInitData The {@link Format#drmInitData}.
+ * @return The builder.
+ */
+ public Builder setDrmInitData(@Nullable DrmInitData drmInitData) {
+ this.drmInitData = drmInitData;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#subsampleOffsetUs}. The default value is {@link #OFFSET_SAMPLE_RELATIVE}.
+ *
+ * @param subsampleOffsetUs The {@link Format#subsampleOffsetUs}.
+ * @return The builder.
+ */
+ public Builder setSubsampleOffsetUs(long subsampleOffsetUs) {
+ this.subsampleOffsetUs = subsampleOffsetUs;
+ return this;
+ }
+
+ // Video specific.
+
+ /**
+ * Sets {@link Format#width}. The default value is {@link #NO_VALUE}.
+ *
+ * @param width The {@link Format#width}.
+ * @return The builder.
+ */
+ public Builder setWidth(int width) {
+ this.width = width;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#height}. The default value is {@link #NO_VALUE}.
+ *
+ * @param height The {@link Format#height}.
+ * @return The builder.
+ */
+ public Builder setHeight(int height) {
+ this.height = height;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#frameRate}. The default value is {@link #NO_VALUE}.
+ *
+ * @param frameRate The {@link Format#frameRate}.
+ * @return The builder.
+ */
+ public Builder setFrameRate(float frameRate) {
+ this.frameRate = frameRate;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#rotationDegrees}. The default value is 0.
+ *
+ * @param rotationDegrees The {@link Format#rotationDegrees}.
+ * @return The builder.
+ */
+ public Builder setRotationDegrees(int rotationDegrees) {
+ this.rotationDegrees = rotationDegrees;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#pixelWidthHeightRatio}. The default value is 1.0f.
+ *
+ * @param pixelWidthHeightRatio The {@link Format#pixelWidthHeightRatio}.
+ * @return The builder.
+ */
+ public Builder setPixelWidthHeightRatio(float pixelWidthHeightRatio) {
+ this.pixelWidthHeightRatio = pixelWidthHeightRatio;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#projectionData}. The default value is {@code null}.
+ *
+ * @param projectionData The {@link Format#projectionData}.
+ * @return The builder.
+ */
+ public Builder setProjectionData(@Nullable byte[] projectionData) {
+ this.projectionData = projectionData;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#stereoMode}. The default value is {@link #NO_VALUE}.
+ *
+ * @param stereoMode The {@link Format#stereoMode}.
+ * @return The builder.
+ */
+ public Builder setStereoMode(@C.StereoMode int stereoMode) {
+ this.stereoMode = stereoMode;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#colorInfo}. The default value is {@code null}.
+ *
+ * @param colorInfo The {@link Format#colorInfo}.
+ * @return The builder.
+ */
+ public Builder setColorInfo(@Nullable ColorInfo colorInfo) {
+ this.colorInfo = colorInfo;
+ return this;
+ }
+
+ // Audio specific.
+
+ /**
+ * Sets {@link Format#channelCount}. The default value is {@link #NO_VALUE}.
+ *
+ * @param channelCount The {@link Format#channelCount}.
+ * @return The builder.
+ */
+ public Builder setChannelCount(int channelCount) {
+ this.channelCount = channelCount;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#sampleRate}. The default value is {@link #NO_VALUE}.
+ *
+ * @param sampleRate The {@link Format#sampleRate}.
+ * @return The builder.
+ */
+ public Builder setSampleRate(int sampleRate) {
+ this.sampleRate = sampleRate;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#pcmEncoding}. The default value is {@link #NO_VALUE}.
+ *
+ * @param pcmEncoding The {@link Format#pcmEncoding}.
+ * @return The builder.
+ */
+ public Builder setPcmEncoding(@C.PcmEncoding int pcmEncoding) {
+ this.pcmEncoding = pcmEncoding;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#encoderDelay}. The default value is 0.
+ *
+ * @param encoderDelay The {@link Format#encoderDelay}.
+ * @return The builder.
+ */
+ public Builder setEncoderDelay(int encoderDelay) {
+ this.encoderDelay = encoderDelay;
+ return this;
+ }
+
+ /**
+ * Sets {@link Format#encoderPadding}. The default value is 0.
+ *
+ * @param encoderPadding The {@link Format#encoderPadding}.
+ * @return The builder.
+ */
+ public Builder setEncoderPadding(int encoderPadding) {
+ this.encoderPadding = encoderPadding;
+ return this;
+ }
+
+ // Text specific.
+
+ /**
+ * Sets {@link Format#accessibilityChannel}. The default value is {@link #NO_VALUE}.
+ *
+ * @param accessibilityChannel The {@link Format#accessibilityChannel}.
+ * @return The builder.
+ */
+ public Builder setAccessibilityChannel(int accessibilityChannel) {
+ this.accessibilityChannel = accessibilityChannel;
+ return this;
+ }
+
+ // Provided by source.
+
+ /**
+ * Sets {@link Format#exoMediaCryptoType}. The default value is {@code null}.
+ *
+ * @param exoMediaCryptoType The {@link Format#exoMediaCryptoType}.
+ * @return The builder.
+ */
+ public Builder setExoMediaCryptoType(
+ @Nullable Class<? extends ExoMediaCrypto> exoMediaCryptoType) {
+ this.exoMediaCryptoType = exoMediaCryptoType;
+ return this;
+ }
+
+ // Build.
+
+ public Format build() {
+ return new Format(
+ id,
+ label,
+ language,
+ selectionFlags,
+ roleFlags,
+ averageBitrate,
+ peakBitrate,
+ codecs,
+ metadata,
+ containerMimeType,
+ sampleMimeType,
+ maxInputSize,
+ initializationData,
+ drmInitData,
+ subsampleOffsetUs,
+ width,
+ height,
+ frameRate,
+ rotationDegrees,
+ pixelWidthHeightRatio,
+ projectionData,
+ stereoMode,
+ colorInfo,
+ channelCount,
+ sampleRate,
+ pcmEncoding,
+ encoderDelay,
+ encoderPadding,
+ accessibilityChannel,
+ exoMediaCryptoType);
+ }
+ }
+
+ /** A value for various fields to indicate that the field's value is unknown or not applicable. */
public static final int NO_VALUE = -1;
/**
@@ -50,12 +637,56 @@
@Nullable public final String id;
/** The human readable label, or null if unknown or not applicable. */
@Nullable public final String label;
+ /** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */
+ @Nullable public final String language;
/** Track selection flags. */
@C.SelectionFlags public final int selectionFlags;
/** Track role flags. */
@C.RoleFlags public final int roleFlags;
/**
- * The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
+ * The average bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The
+ * way in which this field is populated depends on the type of media to which the format
+ * corresponds:
+ *
+ * <ul>
+ * <li>DASH representations: Always {@link Format#NO_VALUE}.
+ * <li>HLS variants: The {@code AVERAGE-BANDWIDTH} attribute defined on the corresponding {@code
+ * EXT-X-STREAM-INF} tag in the master playlist, or {@link Format#NO_VALUE} if not present.
+ * <li>SmoothStreaming track elements: The {@code Bitrate} attribute defined on the
+ * corresponding {@code TrackElement} in the manifest, or {@link Format#NO_VALUE} if not
+ * present.
+ * <li>Progressive container formats: Often {@link Format#NO_VALUE}, but may be populated with
+ * the average bitrate of the container if known.
+ * <li>Sample formats: Often {@link Format#NO_VALUE}, but may be populated with the average
+ * bitrate of the stream of samples with type {@link #sampleMimeType} if known. Note that if
+ * {@link #sampleMimeType} is a compressed format (e.g., {@link MimeTypes#AUDIO_AAC}), then
+ * this bitrate is for the stream of still compressed samples.
+ * </ul>
+ */
+ public final int averageBitrate;
+ /**
+ * The peak bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The way
+ * in which this field is populated depends on the type of media to which the format corresponds:
+ *
+ * <ul>
+ * <li>DASH representations: The {@code @bandwidth} attribute of the corresponding {@code
+ * Representation} element in the manifest.
+ * <li>HLS variants: The {@code BANDWIDTH} attribute defined on the corresponding {@code
+ * EXT-X-STREAM-INF} tag.
+ * <li>SmoothStreaming track elements: Always {@link Format#NO_VALUE}.
+ * <li>Progressive container formats: Often {@link Format#NO_VALUE}, but may be populated with
+ * the peak bitrate of the container if known.
+ * <li>Sample formats: Often {@link Format#NO_VALUE}, but may be populated with the peak bitrate
+ * of the stream of samples with type {@link #sampleMimeType} if known. Note that if {@link
+ * #sampleMimeType} is a compressed format (e.g., {@link MimeTypes#AUDIO_AAC}), then this
+ * bitrate is for the stream of still compressed samples.
+ * </ul>
+ */
+ public final int peakBitrate;
+ /**
+ * The bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate
+ * if known, or else {@link Format#NO_VALUE}. Equivalent to: {@code peakBitrate != NO_VALUE ?
+ * peakBitrate : averageBitrate}.
*/
public final int bitrate;
/** Codecs of the format as described in RFC 6381, or null if unknown or not applicable. */
@@ -68,12 +699,9 @@
/** The mime type of the container, or null if unknown or not applicable. */
@Nullable public final String containerMimeType;
- // Elementary stream specific.
+ // Sample specific.
- /**
- * The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not
- * applicable.
- */
+ /** The sample mime type, or null if unknown or not applicable. */
@Nullable public final String sampleMimeType;
/**
* The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or
@@ -116,16 +744,15 @@
public final int rotationDegrees;
/** The width to height ratio of pixels in the video, or 1.0 if unknown or not applicable. */
public final float pixelWidthHeightRatio;
+ /** The projection data for 360/VR video, or null if not applicable. */
+ @Nullable public final byte[] projectionData;
/**
* The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}.
*/
- @C.StereoMode
- public final int stereoMode;
- /** The projection data for 360/VR video, or null if not applicable. */
- @Nullable public final byte[] projectionData;
- /** The color metadata associated with the video, helps with accurate color reproduction. */
+ @C.StereoMode public final int stereoMode;
+ /** The color metadata associated with the video, or null if not applicable. */
@Nullable public final ColorInfo colorInfo;
// Audio specific.
@@ -139,7 +766,7 @@
*/
public final int sampleRate;
/** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */
- public final @C.PcmEncoding int pcmEncoding;
+ @C.PcmEncoding public final int pcmEncoding;
/**
* The number of frames to trim from the start of the decoded audio stream, or 0 if not
* applicable.
@@ -150,21 +777,17 @@
*/
public final int encoderPadding;
- // Audio and text specific.
+ // Text specific.
- /** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */
- @Nullable public final String language;
- /**
- * The Accessibility channel, or {@link #NO_VALUE} if not known or applicable.
- */
+ /** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */
public final int accessibilityChannel;
// Provided by source.
/**
* The type of the {@link ExoMediaCrypto} provided by the media source, if the media source can
- * acquire a {@link DrmSession} for {@link #drmInitData}. Null if the media source cannot acquire
- * a session for {@link #drmInitData}, or if not applicable.
+ * acquire a DRM session for {@link #drmInitData}. Null if the media source cannot acquire a
+ * session for {@link #drmInitData}, or if not applicable.
*/
@Nullable public final Class<? extends ExoMediaCrypto> exoMediaCryptoType;
@@ -173,40 +796,10 @@
// Video.
- /**
- * @deprecated Use {@link #createVideoContainerFormat(String, String, String, String, String,
- * Metadata, int, int, int, float, List, int, int)} instead.
- */
+ /** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createVideoContainerFormat(
@Nullable String id,
- @Nullable String containerMimeType,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- int width,
- int height,
- float frameRate,
- @Nullable List<byte[]> initializationData,
- @C.SelectionFlags int selectionFlags) {
- return createVideoContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- bitrate,
- width,
- height,
- frameRate,
- initializationData,
- selectionFlags,
- /* roleFlags= */ 0);
- }
-
- public static Format createVideoContainerFormat(
- @Nullable String id,
@Nullable String label,
@Nullable String containerMimeType,
@Nullable String sampleMimeType,
@@ -219,38 +812,26 @@
@Nullable List<byte[]> initializationData,
@C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- initializationData,
- /* drmInitData= */ null,
- OFFSET_SAMPLE_RELATIVE,
- width,
- height,
- frameRate,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- /* language= */ null,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLabel(label)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setMetadata(metadata)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setInitializationData(initializationData)
+ .setWidth(width)
+ .setHeight(height)
+ .setFrameRate(frameRate)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createVideoSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -262,21 +843,23 @@
float frameRate,
@Nullable List<byte[]> initializationData,
@Nullable DrmInitData drmInitData) {
- return createVideoSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- maxInputSize,
- width,
- height,
- frameRate,
- initializationData,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- drmInitData);
+ return new Builder()
+ .setId(id)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setWidth(width)
+ .setHeight(height)
+ .setFrameRate(frameRate)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createVideoSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -290,24 +873,25 @@
int rotationDegrees,
float pixelWidthHeightRatio,
@Nullable DrmInitData drmInitData) {
- return createVideoSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- maxInputSize,
- width,
- height,
- frameRate,
- initializationData,
- rotationDegrees,
- pixelWidthHeightRatio,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- drmInitData);
+ return new Builder()
+ .setId(id)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setWidth(width)
+ .setHeight(height)
+ .setFrameRate(frameRate)
+ .setRotationDegrees(rotationDegrees)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createVideoSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -324,74 +908,32 @@
@C.StereoMode int stereoMode,
@Nullable ColorInfo colorInfo,
@Nullable DrmInitData drmInitData) {
- return new Format(
- id,
- /* label= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- bitrate,
- codecs,
- /* metadata= */ null,
- /* containerMimeType= */ null,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- /* language= */ null,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setWidth(width)
+ .setHeight(height)
+ .setFrameRate(frameRate)
+ .setRotationDegrees(rotationDegrees)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .setProjectionData(projectionData)
+ .setStereoMode(stereoMode)
+ .setColorInfo(colorInfo)
+ .build();
}
// Audio.
- /**
- * @deprecated Use {@link #createAudioContainerFormat(String, String, String, String, String,
- * Metadata, int, int, int, List, int, int, String)} instead.
- */
+ /** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createAudioContainerFormat(
@Nullable String id,
- @Nullable String containerMimeType,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- int channelCount,
- int sampleRate,
- @Nullable List<byte[]> initializationData,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language) {
- return createAudioContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- bitrate,
- channelCount,
- sampleRate,
- initializationData,
- selectionFlags,
- /* roleFlags= */ 0,
- language);
- }
-
- public static Format createAudioContainerFormat(
- @Nullable String id,
@Nullable String label,
@Nullable String containerMimeType,
@Nullable String sampleMimeType,
@@ -404,38 +946,26 @@
@C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags,
@Nullable String language) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- initializationData,
- /* drmInitData= */ null,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- channelCount,
- sampleRate,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- language,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLabel(label)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setMetadata(metadata)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setInitializationData(initializationData)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createAudioSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -448,21 +978,24 @@
@Nullable DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags,
@Nullable String language) {
- return createAudioSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- maxInputSize,
- channelCount,
- sampleRate,
- /* pcmEncoding= */ NO_VALUE,
- initializationData,
- drmInitData,
- selectionFlags,
- language);
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createAudioSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -476,24 +1009,25 @@
@Nullable DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags,
@Nullable String language) {
- return createAudioSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- maxInputSize,
- channelCount,
- sampleRate,
- pcmEncoding,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- initializationData,
- drmInitData,
- selectionFlags,
- language,
- /* metadata= */ null);
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setPcmEncoding(pcmEncoding)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createAudioSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@@ -510,40 +1044,30 @@
@C.SelectionFlags int selectionFlags,
@Nullable String language,
@Nullable Metadata metadata) {
- return new Format(
- id,
- /* label= */ null,
- selectionFlags,
- /* roleFlags= */ 0,
- bitrate,
- codecs,
- metadata,
- /* containerMimeType= */ null,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setMetadata(metadata)
+ .setSampleMimeType(sampleMimeType)
+ .setMaxInputSize(maxInputSize)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setPcmEncoding(pcmEncoding)
+ .setEncoderDelay(encoderDelay)
+ .setEncoderPadding(encoderPadding)
+ .build();
}
// Text.
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createTextContainerFormat(
@Nullable String id,
@Nullable String label,
@@ -554,19 +1078,22 @@
@C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags,
@Nullable String language) {
- return createTextContainerFormat(
- id,
- label,
- containerMimeType,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- roleFlags,
- language,
- /* accessibilityChannel= */ NO_VALUE);
+ return new Builder()
+ .setId(id)
+ .setLabel(label)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createTextContainerFormat(
@Nullable String id,
@Nullable String label,
@@ -578,224 +1105,82 @@
@C.RoleFlags int roleFlags,
@Nullable String language,
int accessibilityChannel) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- /* metadata= */ null,
- containerMimeType,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- language,
- accessibilityChannel,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLabel(label)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setAccessibilityChannel(accessibilityChannel)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createTextSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@C.SelectionFlags int selectionFlags,
@Nullable String language) {
- return createTextSampleFormat(id, sampleMimeType, selectionFlags, language, null);
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setSampleMimeType(sampleMimeType)
+ .build();
}
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createTextSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
@C.SelectionFlags int selectionFlags,
@Nullable String language,
- @Nullable DrmInitData drmInitData) {
- return createTextSampleFormat(
- id,
- sampleMimeType,
- /* codecs= */ null,
- /* bitrate= */ NO_VALUE,
- selectionFlags,
- language,
- NO_VALUE,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- Collections.emptyList());
- }
-
- public static Format createTextSampleFormat(
- @Nullable String id,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language,
int accessibilityChannel,
- @Nullable DrmInitData drmInitData) {
- return createTextSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- language,
- accessibilityChannel,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- Collections.emptyList());
- }
-
- public static Format createTextSampleFormat(
- @Nullable String id,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language,
- @Nullable DrmInitData drmInitData,
- long subsampleOffsetUs) {
- return createTextSampleFormat(
- id,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- language,
- /* accessibilityChannel= */ NO_VALUE,
- drmInitData,
- subsampleOffsetUs,
- Collections.emptyList());
- }
-
- public static Format createTextSampleFormat(
- @Nullable String id,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language,
- int accessibilityChannel,
- @Nullable DrmInitData drmInitData,
long subsampleOffsetUs,
@Nullable List<byte[]> initializationData) {
- return new Format(
- id,
- /* label= */ null,
- selectionFlags,
- /* roleFlags= */ 0,
- bitrate,
- codecs,
- /* metadata= */ null,
- /* containerMimeType= */ null,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- language,
- accessibilityChannel,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setSampleMimeType(sampleMimeType)
+ .setInitializationData(initializationData)
+ .setSubsampleOffsetUs(subsampleOffsetUs)
+ .setAccessibilityChannel(accessibilityChannel)
+ .build();
}
// Image.
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
public static Format createImageSampleFormat(
@Nullable String id,
@Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
@C.SelectionFlags int selectionFlags,
@Nullable List<byte[]> initializationData,
- @Nullable String language,
- @Nullable DrmInitData drmInitData) {
- return new Format(
- id,
- /* label= */ null,
- selectionFlags,
- /* roleFlags= */ 0,
- bitrate,
- codecs,
- /* metadata=*/ null,
- /* containerMimeType= */ null,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- initializationData,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- language,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ @Nullable String language) {
+ return new Builder()
+ .setId(id)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setSampleMimeType(sampleMimeType)
+ .setInitializationData(initializationData)
+ .build();
}
// Generic.
- /**
- * @deprecated Use {@link #createContainerFormat(String, String, String, String, String, int, int,
- * int, String)} instead.
- */
+ /** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createContainerFormat(
@Nullable String id,
- @Nullable String containerMimeType,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language) {
- return createContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- /* roleFlags= */ 0,
- language);
- }
-
- public static Format createContainerFormat(
- @Nullable String id,
@Nullable String label,
@Nullable String containerMimeType,
@Nullable String sampleMimeType,
@@ -804,121 +1189,39 @@
@C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags,
@Nullable String language) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- /* metadata= */ null,
- containerMimeType,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- language,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ return new Builder()
+ .setId(id)
+ .setLabel(label)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(bitrate)
+ .setPeakBitrate(bitrate)
+ .setCodecs(codecs)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .build();
}
- public static Format createSampleFormat(
- @Nullable String id, @Nullable String sampleMimeType, long subsampleOffsetUs) {
- return new Format(
- id,
- /* label= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* bitrate= */ NO_VALUE,
- /* codecs= */ null,
- /* metadata= */ null,
- /* containerMimeType= */ null,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- subsampleOffsetUs,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- /* language= */ null,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
- }
-
- public static Format createSampleFormat(
- @Nullable String id,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- int bitrate,
- @Nullable DrmInitData drmInitData) {
- return new Format(
- id,
- /* label= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- bitrate,
- codecs,
- /* metadata= */ null,
- /* containerMimeType= */ null,
- sampleMimeType,
- /* maxInputSize= */ NO_VALUE,
- /* initializationData= */ null,
- drmInitData,
- OFFSET_SAMPLE_RELATIVE,
- /* width= */ NO_VALUE,
- /* height= */ NO_VALUE,
- /* frameRate= */ NO_VALUE,
- /* rotationDegrees= */ NO_VALUE,
- /* pixelWidthHeightRatio= */ NO_VALUE,
- /* projectionData= */ null,
- /* stereoMode= */ NO_VALUE,
- /* colorInfo= */ null,
- /* channelCount= */ NO_VALUE,
- /* sampleRate= */ NO_VALUE,
- /* pcmEncoding= */ NO_VALUE,
- /* encoderDelay= */ NO_VALUE,
- /* encoderPadding= */ NO_VALUE,
- /* language= */ null,
- /* accessibilityChannel= */ NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ /** @deprecated Use {@link Format.Builder}. */
+ @Deprecated
+ public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
+ return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
}
/* package */ Format(
@Nullable String id,
@Nullable String label,
+ @Nullable String language,
@C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags,
- int bitrate,
+ int averageBitrate,
+ int peakBitrate,
@Nullable String codecs,
@Nullable Metadata metadata,
// Container specific.
@Nullable String containerMimeType,
- // Elementary stream specific.
+ // Sample specific.
@Nullable String sampleMimeType,
int maxInputSize,
@Nullable List<byte[]> initializationData,
@@ -939,21 +1242,23 @@
@C.PcmEncoding int pcmEncoding,
int encoderDelay,
int encoderPadding,
- // Audio and text specific.
- @Nullable String language,
+ // Text specific.
int accessibilityChannel,
// Provided by source.
@Nullable Class<? extends ExoMediaCrypto> exoMediaCryptoType) {
this.id = id;
this.label = label;
+ this.language = Util.normalizeLanguageCode(language);
this.selectionFlags = selectionFlags;
this.roleFlags = roleFlags;
- this.bitrate = bitrate;
+ this.averageBitrate = averageBitrate;
+ this.peakBitrate = peakBitrate;
+ this.bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
this.codecs = codecs;
this.metadata = metadata;
// Container specific.
this.containerMimeType = containerMimeType;
- // Elementary stream specific.
+ // Sample specific.
this.sampleMimeType = sampleMimeType;
this.maxInputSize = maxInputSize;
this.initializationData =
@@ -964,9 +1269,8 @@
this.width = width;
this.height = height;
this.frameRate = frameRate;
- this.rotationDegrees = rotationDegrees == Format.NO_VALUE ? 0 : rotationDegrees;
- this.pixelWidthHeightRatio =
- pixelWidthHeightRatio == Format.NO_VALUE ? 1 : pixelWidthHeightRatio;
+ this.rotationDegrees = rotationDegrees == NO_VALUE ? 0 : rotationDegrees;
+ this.pixelWidthHeightRatio = pixelWidthHeightRatio == NO_VALUE ? 1 : pixelWidthHeightRatio;
this.projectionData = projectionData;
this.stereoMode = stereoMode;
this.colorInfo = colorInfo;
@@ -974,10 +1278,9 @@
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding;
- this.encoderDelay = encoderDelay == Format.NO_VALUE ? 0 : encoderDelay;
- this.encoderPadding = encoderPadding == Format.NO_VALUE ? 0 : encoderPadding;
- // Audio and text specific.
- this.language = Util.normalizeLanguageCode(language);
+ this.encoderDelay = encoderDelay == NO_VALUE ? 0 : encoderDelay;
+ this.encoderPadding = encoderPadding == NO_VALUE ? 0 : encoderPadding;
+ // Text specific.
this.accessibilityChannel = accessibilityChannel;
// Provided by source.
this.exoMediaCryptoType = exoMediaCryptoType;
@@ -987,14 +1290,17 @@
/* package */ Format(Parcel in) {
id = in.readString();
label = in.readString();
+ language = in.readString();
selectionFlags = in.readInt();
roleFlags = in.readInt();
- bitrate = in.readInt();
+ averageBitrate = in.readInt();
+ peakBitrate = in.readInt();
+ bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
codecs = in.readString();
metadata = in.readParcelable(Metadata.class.getClassLoader());
// Container specific.
containerMimeType = in.readString();
- // Elementary stream specific.
+ // Sample specific.
sampleMimeType = in.readString();
maxInputSize = in.readInt();
int initializationDataSize = in.readInt();
@@ -1020,163 +1326,43 @@
pcmEncoding = in.readInt();
encoderDelay = in.readInt();
encoderPadding = in.readInt();
- // Audio and text specific.
- language = in.readString();
+ // Text specific.
accessibilityChannel = in.readInt();
// Provided by source.
exoMediaCryptoType = null;
}
+ /** Returns a {@link Format.Builder} initialized with the values of this instance. */
+ public Builder buildUpon() {
+ return new Builder(this);
+ }
+
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */
+ @Deprecated
public Format copyWithMaxInputSize(int maxInputSize) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setMaxInputSize(maxInputSize).build();
}
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */
+ @Deprecated
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
}
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */
+ @Deprecated
public Format copyWithLabel(@Nullable String label) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setLabel(label).build();
}
- public Format copyWithContainerInfo(
- @Nullable String id,
- @Nullable String label,
- @Nullable String sampleMimeType,
- @Nullable String codecs,
- @Nullable Metadata metadata,
- int bitrate,
- int width,
- int height,
- int channelCount,
- @C.SelectionFlags int selectionFlags,
- @Nullable String language) {
-
- if (this.metadata != null) {
- metadata = this.metadata.copyWithAppendedEntriesFrom(metadata);
- }
-
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ /** @deprecated Use {@link #withManifestFormatInfo(Format)}. */
+ @Deprecated
+ public Format copyWithManifestFormatInfo(Format manifestFormat) {
+ return withManifestFormatInfo(manifestFormat);
}
@SuppressWarnings("ReferenceEquality")
- public Format copyWithManifestFormatInfo(Format manifestFormat) {
+ public Format withManifestFormatInfo(Format manifestFormat) {
if (this == manifestFormat) {
// No need to copy from ourselves.
return this;
@@ -1185,28 +1371,31 @@
int trackType = MimeTypes.getTrackType(sampleMimeType);
// Use manifest value only.
- String id = manifestFormat.id;
+ @Nullable String id = manifestFormat.id;
// Prefer manifest values, but fill in from sample format if missing.
- String label = manifestFormat.label != null ? manifestFormat.label : this.label;
- String language = this.language;
+ @Nullable String label = manifestFormat.label != null ? manifestFormat.label : this.label;
+ @Nullable String language = this.language;
if ((trackType == C.TRACK_TYPE_TEXT || trackType == C.TRACK_TYPE_AUDIO)
&& manifestFormat.language != null) {
language = manifestFormat.language;
}
// Prefer sample format values, but fill in from manifest if missing.
- int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
- String codecs = this.codecs;
+ int averageBitrate =
+ this.averageBitrate == NO_VALUE ? manifestFormat.averageBitrate : this.averageBitrate;
+ int peakBitrate = this.peakBitrate == NO_VALUE ? manifestFormat.peakBitrate : this.peakBitrate;
+ @Nullable String codecs = this.codecs;
if (codecs == null) {
// The manifest format may be muxed, so filter only codecs of this format's type. If we still
// have more than one codec then we're unable to uniquely identify which codec to fill in.
- String codecsOfType = Util.getCodecsOfType(manifestFormat.codecs, trackType);
+ @Nullable String codecsOfType = Util.getCodecsOfType(manifestFormat.codecs, trackType);
if (Util.splitCodecs(codecsOfType).length == 1) {
codecs = codecsOfType;
}
}
+ @Nullable
Metadata metadata =
this.metadata == null
? manifestFormat.metadata
@@ -1220,284 +1409,74 @@
// Merge manifest and sample format values.
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
@C.RoleFlags int roleFlags = this.roleFlags | manifestFormat.roleFlags;
+ @Nullable
DrmInitData drmInitData =
DrmInitData.createSessionCreationData(manifestFormat.drmInitData, this.drmInitData);
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon()
+ .setId(id)
+ .setLabel(label)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setAverageBitrate(averageBitrate)
+ .setPeakBitrate(peakBitrate)
+ .setCodecs(codecs)
+ .setMetadata(metadata)
+ .setDrmInitData(drmInitData)
+ .setFrameRate(frameRate)
+ .build();
}
+ /**
+ * @deprecated Use {@link #buildUpon()}, {@link Builder#setEncoderDelay(int)} and {@link
+ * Builder#setEncoderPadding(int)}.
+ */
+ @Deprecated
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
}
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */
+ @Deprecated
public Format copyWithFrameRate(float frameRate) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setFrameRate(frameRate).build();
}
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */
+ @Deprecated
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
- return copyWithAdjustments(drmInitData, metadata);
+ return buildUpon().setDrmInitData(drmInitData).build();
}
+ /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */
+ @Deprecated
public Format copyWithMetadata(@Nullable Metadata metadata) {
- return copyWithAdjustments(drmInitData, metadata);
+ return buildUpon().setMetadata(metadata).build();
}
- @SuppressWarnings("ReferenceEquality")
- public Format copyWithAdjustments(
- @Nullable DrmInitData drmInitData, @Nullable Metadata metadata) {
- if (drmInitData == this.drmInitData && metadata == this.metadata) {
- return this;
- }
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
- }
-
- public Format copyWithRotationDegrees(int rotationDegrees) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
- }
-
+ /**
+ * @deprecated Use {@link #buildUpon()} and {@link Builder#setAverageBitrate(int)} and {@link
+ * Builder#setPeakBitrate(int)}.
+ */
+ @Deprecated
public Format copyWithBitrate(int bitrate) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setAverageBitrate(bitrate).setPeakBitrate(bitrate).build();
}
+ /**
+ * @deprecated Use {@link #buildUpon()}, {@link Builder#setWidth(int)} and {@link
+ * Builder#setHeight(int)}.
+ */
+ @Deprecated
public Format copyWithVideoSize(int width, int height) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setWidth(width).setHeight(height).build();
}
+ /** Returns a copy of this format with the specified {@link #exoMediaCryptoType}. */
public Format copyWithExoMediaCryptoType(
@Nullable Class<? extends ExoMediaCrypto> exoMediaCryptoType) {
- return new Format(
- id,
- label,
- selectionFlags,
- roleFlags,
- bitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- language,
- accessibilityChannel,
- exoMediaCryptoType);
+ return buildUpon().setExoMediaCryptoType(exoMediaCryptoType).build();
}
/**
@@ -1545,14 +1524,16 @@
int result = 17;
result = 31 * result + (id == null ? 0 : id.hashCode());
result = 31 * result + (label != null ? label.hashCode() : 0);
+ result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + selectionFlags;
result = 31 * result + roleFlags;
- result = 31 * result + bitrate;
+ result = 31 * result + averageBitrate;
+ result = 31 * result + peakBitrate;
result = 31 * result + (codecs == null ? 0 : codecs.hashCode());
result = 31 * result + (metadata == null ? 0 : metadata.hashCode());
// Container specific.
result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode());
- // Elementary stream specific.
+ // Sample specific.
result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode());
result = 31 * result + maxInputSize;
// [Omitted] initializationData.
@@ -1573,8 +1554,7 @@
result = 31 * result + pcmEncoding;
result = 31 * result + encoderDelay;
result = 31 * result + encoderPadding;
- // Audio and text specific.
- result = 31 * result + (language == null ? 0 : language.hashCode());
+ // Text specific.
result = 31 * result + accessibilityChannel;
// Provided by source.
result = 31 * result + (exoMediaCryptoType == null ? 0 : exoMediaCryptoType.hashCode());
@@ -1598,7 +1578,8 @@
// Field equality checks ordered by type, with the cheapest checks first.
return selectionFlags == other.selectionFlags
&& roleFlags == other.roleFlags
- && bitrate == other.bitrate
+ && averageBitrate == other.averageBitrate
+ && peakBitrate == other.peakBitrate
&& maxInputSize == other.maxInputSize
&& subsampleOffsetUs == other.subsampleOffsetUs
&& width == other.width
@@ -1656,22 +1637,22 @@
}
StringBuilder builder = new StringBuilder();
builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType);
- if (format.bitrate != Format.NO_VALUE) {
+ if (format.bitrate != NO_VALUE) {
builder.append(", bitrate=").append(format.bitrate);
}
if (format.codecs != null) {
builder.append(", codecs=").append(format.codecs);
}
- if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
+ if (format.width != NO_VALUE && format.height != NO_VALUE) {
builder.append(", res=").append(format.width).append("x").append(format.height);
}
- if (format.frameRate != Format.NO_VALUE) {
+ if (format.frameRate != NO_VALUE) {
builder.append(", fps=").append(format.frameRate);
}
- if (format.channelCount != Format.NO_VALUE) {
+ if (format.channelCount != NO_VALUE) {
builder.append(", channels=").append(format.channelCount);
}
- if (format.sampleRate != Format.NO_VALUE) {
+ if (format.sampleRate != NO_VALUE) {
builder.append(", sample_rate=").append(format.sampleRate);
}
if (format.language != null) {
@@ -1694,14 +1675,16 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(label);
+ dest.writeString(language);
dest.writeInt(selectionFlags);
dest.writeInt(roleFlags);
- dest.writeInt(bitrate);
+ dest.writeInt(averageBitrate);
+ dest.writeInt(peakBitrate);
dest.writeString(codecs);
dest.writeParcelable(metadata, 0);
// Container specific.
dest.writeString(containerMimeType);
- // Elementary stream specific.
+ // Sample specific.
dest.writeString(sampleMimeType);
dest.writeInt(maxInputSize);
int initializationDataSize = initializationData.size();
@@ -1729,8 +1712,7 @@
dest.writeInt(pcmEncoding);
dest.writeInt(encoderDelay);
dest.writeInt(encoderPadding);
- // Audio and text specific.
- dest.writeString(language);
+ // Text specific.
dest.writeInt(accessibilityChannel);
}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
new file mode 100644
index 0000000..a78a2d0
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import android.net.Uri;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.offline.StreamKey;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/** Representation of a media item. */
+public final class MediaItem {
+
+ /**
+ * Creates a {@link MediaItem} for the given source uri.
+ *
+ * @param sourceUri The source uri.
+ * @return An {@link MediaItem} for the given source uri.
+ */
+ public static MediaItem fromUri(String sourceUri) {
+ return new MediaItem.Builder().setSourceUri(sourceUri).build();
+ }
+
+ /**
+ * Creates a {@link MediaItem} for the given {@link Uri source uri}.
+ *
+ * @param sourceUri The {@link Uri source uri}.
+ * @return An {@link MediaItem} for the given source uri.
+ */
+ public static MediaItem fromUri(Uri sourceUri) {
+ return new MediaItem.Builder().setSourceUri(sourceUri).build();
+ }
+
+ /** A builder for {@link MediaItem} instances. */
+ public static final class Builder {
+
+ @Nullable private String mediaId;
+ @Nullable private Uri sourceUri;
+ @Nullable private String mimeType;
+ private long clipStartPositionMs;
+ private long clipEndPositionMs;
+ private boolean clipRelativeToLiveWindow;
+ private boolean clipRelativeToDefaultPosition;
+ private boolean clipStartsAtKeyFrame;
+ @Nullable private Uri drmLicenseUri;
+ private Map<String, String> drmLicenseRequestHeaders;
+ @Nullable private UUID drmUuid;
+ private boolean drmMultiSession;
+ private boolean drmPlayClearContentWithoutKey;
+ private List<Integer> drmSessionForClearTypes;
+ private List<StreamKey> streamKeys;
+ private List<Subtitle> subtitles;
+ @Nullable private Object tag;
+ @Nullable private MediaMetadata mediaMetadata;
+
+ /** Creates a builder. */
+ public Builder() {
+ streamKeys = Collections.emptyList();
+ subtitles = Collections.emptyList();
+ drmSessionForClearTypes = Collections.emptyList();
+ drmLicenseRequestHeaders = Collections.emptyMap();
+ clipEndPositionMs = C.TIME_END_OF_SOURCE;
+ }
+
+ /**
+ * Sets the optional media id which identifies the media item. If not specified, {@link
+ * #setSourceUri} must be called and the string representation of {@link
+ * PlaybackProperties#sourceUri} is used as the media id.
+ */
+ public Builder setMediaId(@Nullable String mediaId) {
+ this.mediaId = mediaId;
+ return this;
+ }
+
+ /**
+ * Sets the optional source uri. If not specified, {@link #setMediaId(String)} must be called.
+ */
+ public Builder setSourceUri(@Nullable String sourceUri) {
+ return setSourceUri(sourceUri == null ? null : Uri.parse(sourceUri));
+ }
+
+ /**
+ * Sets the optional source {@link Uri}. If not specified, {@link #setMediaId(String)} must be
+ * called.
+ */
+ public Builder setSourceUri(@Nullable Uri sourceUri) {
+ this.sourceUri = sourceUri;
+ return this;
+ }
+
+ /**
+ * Sets the optional mime type.
+ *
+ * <p>The mime type may be used as a hint for inferring the type of the media item.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the mime type is used to create a {@link
+ * PlaybackProperties} object. Otherwise it will be ignored.
+ *
+ * @param mimeType The mime type.
+ */
+ public Builder setMimeType(@Nullable String mimeType) {
+ this.mimeType = mimeType;
+ return this;
+ }
+
+ /**
+ * Sets the optional start position in milliseconds which must be a value larger than or equal
+ * to zero (Default: 0).
+ */
+ public Builder setClipStartPositionMs(long startPositionMs) {
+ Assertions.checkArgument(startPositionMs >= 0);
+ this.clipStartPositionMs = startPositionMs;
+ return this;
+ }
+
+ /**
+ * Sets the optional end position in milliseconds which must be a value larger than or equal to
+ * zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media (Default:
+ * {@link C#TIME_END_OF_SOURCE}).
+ */
+ public Builder setClipEndPositionMs(long endPositionMs) {
+ Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0);
+ this.clipEndPositionMs = endPositionMs;
+ return this;
+ }
+
+ /**
+ * Sets whether the start/end positions should move with the live window for live streams. If
+ * {@code false}, live streams end when playback reaches the end position in live window seen
+ * when the media is first loaded (Default: {@code false}).
+ */
+ public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) {
+ this.clipRelativeToLiveWindow = relativeToLiveWindow;
+ return this;
+ }
+
+ /**
+ * Sets whether the start position and the end position are relative to the default position in
+ * the window (Default: {@code false}).
+ */
+ public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) {
+ this.clipRelativeToDefaultPosition = relativeToDefaultPosition;
+ return this;
+ }
+
+ /**
+ * Sets whether the start point is guaranteed to be a key frame. If {@code false}, the playback
+ * transition into the clip may not be seamless (Default: {@code false}).
+ */
+ public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) {
+ this.clipStartsAtKeyFrame = startsAtKeyFrame;
+ return this;
+ }
+
+ /**
+ * Sets the optional license server {@link Uri}. If a license uri is set, the {@link
+ * DrmConfiguration#uuid} needs to be specified as well.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm license uri is used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setDrmLicenseUri(@Nullable Uri licenseUri) {
+ drmLicenseUri = licenseUri;
+ return this;
+ }
+
+ /**
+ * Sets the optional license server uri as a {@link String}. If a license uri is set, the {@link
+ * DrmConfiguration#uuid} needs to be specified as well.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm license uri is used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setDrmLicenseUri(@Nullable String licenseUri) {
+ drmLicenseUri = licenseUri == null ? null : Uri.parse(licenseUri);
+ return this;
+ }
+
+ /**
+ * Sets the optional request headers attached to the drm license request.
+ *
+ * <p>{@code null} or an empty {@link Map} can be used for a reset.
+ *
+ * <p>If no valid drm configuration is specified, the drm license request headers are ignored.
+ */
+ public Builder setDrmLicenseRequestHeaders(
+ @Nullable Map<String, String> licenseRequestHeaders) {
+ this.drmLicenseRequestHeaders =
+ licenseRequestHeaders != null && !licenseRequestHeaders.isEmpty()
+ ? Collections.unmodifiableMap(new HashMap<>(licenseRequestHeaders))
+ : Collections.emptyMap();
+ return this;
+ }
+
+ /**
+ * Sets the {@link UUID} of the protection scheme. If a drm system uuid is set, the {@link
+ * DrmConfiguration#licenseUri} needs to be set as well.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm system uuid is used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setDrmUuid(@Nullable UUID uuid) {
+ drmUuid = uuid;
+ return this;
+ }
+
+ /**
+ * Sets whether the drm configuration is multi session enabled.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the drm multi session flag is used to
+ * create a {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setDrmMultiSession(boolean multiSession) {
+ drmMultiSession = multiSession;
+ return this;
+ }
+
+ /**
+ * Sets whether clear samples within protected content should be played when keys for the
+ * encrypted part of the content have yet to be loaded.
+ */
+ public Builder setDrmPlayClearContentWithoutKey(boolean playClearContentWithoutKey) {
+ this.drmPlayClearContentWithoutKey = playClearContentWithoutKey;
+ return this;
+ }
+
+ /**
+ * Sets whether a drm session should be used for clear tracks of type {@link C#TRACK_TYPE_VIDEO}
+ * and {@link C#TRACK_TYPE_AUDIO}.
+ *
+ * <p>This method overrides what has been set by previously calling {@link
+ * #setDrmSessionForClearTypes(List)}.
+ */
+ public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
+ this.setDrmSessionForClearTypes(
+ sessionForClearPeriods
+ ? Arrays.asList(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
+ : Collections.emptyList());
+ return this;
+ }
+
+ /**
+ * Sets a list of {@link C}{@code .TRACK_TYPE_*} constants for which to use a drm session even
+ * when the tracks are in the clear.
+ *
+ * <p>For the common case of using a drm session for {@link C#TRACK_TYPE_VIDEO} and {@link
+ * C#TRACK_TYPE_AUDIO} the {@link #setDrmSessionForClearPeriods(boolean)} can be used.
+ *
+ * <p>This method overrides what has been set by previously calling {@link
+ * #setDrmSessionForClearPeriods(boolean)}.
+ *
+ * <p>{@code null} or an empty {@link List} can be used for a reset.
+ */
+ public Builder setDrmSessionForClearTypes(@Nullable List<Integer> sessionForClearTypes) {
+ this.drmSessionForClearTypes =
+ sessionForClearTypes != null && !sessionForClearTypes.isEmpty()
+ ? Collections.unmodifiableList(new ArrayList<>(sessionForClearTypes))
+ : Collections.emptyList();
+ return this;
+ }
+
+ /**
+ * Sets the optional stream keys by which the manifest is filtered (only used for adaptive
+ * streams).
+ *
+ * <p>{@code null} or an empty {@link List} can be used for a reset.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the stream keys are used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setStreamKeys(@Nullable List<StreamKey> streamKeys) {
+ this.streamKeys =
+ streamKeys != null && !streamKeys.isEmpty()
+ ? Collections.unmodifiableList(new ArrayList<>(streamKeys))
+ : Collections.emptyList();
+ return this;
+ }
+
+ /**
+ * Sets the optional subtitles.
+ *
+ * <p>{@code null} or an empty {@link List} can be used for a reset.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the subtitles are used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setSubtitles(@Nullable List<Subtitle> subtitles) {
+ this.subtitles =
+ subtitles != null && !subtitles.isEmpty()
+ ? Collections.unmodifiableList(new ArrayList<>(subtitles))
+ : Collections.emptyList();
+ return this;
+ }
+
+ /**
+ * Sets the optional tag for custom attributes. The tag for the media source which will be
+ * published in the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
+ * com.google.android.exoplayer2.Timeline.Window#tag}.
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the tag is used to create a {@link
+ * PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setTag(@Nullable Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ /** Sets the media metadata. */
+ public Builder setMediaMetadata(MediaMetadata mediaMetadata) {
+ this.mediaMetadata = mediaMetadata;
+ return this;
+ }
+
+ /**
+ * Returns a new {@link MediaItem} instance with the current builder values.
+ */
+ public MediaItem build() {
+ Assertions.checkState(drmLicenseUri == null || drmUuid != null);
+ @Nullable PlaybackProperties playbackProperties = null;
+ if (sourceUri != null) {
+ playbackProperties =
+ new PlaybackProperties(
+ sourceUri,
+ mimeType,
+ drmUuid != null
+ ? new DrmConfiguration(
+ drmUuid,
+ drmLicenseUri,
+ drmLicenseRequestHeaders,
+ drmMultiSession,
+ drmPlayClearContentWithoutKey,
+ drmSessionForClearTypes)
+ : null,
+ streamKeys,
+ subtitles,
+ tag);
+ mediaId = mediaId != null ? mediaId : sourceUri.toString();
+ }
+ return new MediaItem(
+ Assertions.checkNotNull(mediaId),
+ new ClippingProperties(
+ clipStartPositionMs,
+ clipEndPositionMs,
+ clipRelativeToLiveWindow,
+ clipRelativeToDefaultPosition,
+ clipStartsAtKeyFrame),
+ playbackProperties,
+ mediaMetadata != null ? mediaMetadata : new MediaMetadata.Builder().build());
+ }
+ }
+
+ /** DRM configuration for a media item. */
+ public static final class DrmConfiguration {
+
+ /** The UUID of the protection scheme. */
+ public final UUID uuid;
+
+ /**
+ * Optional license server {@link Uri}. If {@code null} then the license server must be
+ * specified by the media.
+ */
+ @Nullable public final Uri licenseUri;
+
+ /** The headers to attach to the request for the license uri. */
+ public final Map<String, String> requestHeaders;
+
+ /** Whether the drm configuration is multi session enabled. */
+ public final boolean multiSession;
+
+ /**
+ * Whether clear samples within protected content should be played when keys for the encrypted
+ * part of the content have yet to be loaded.
+ */
+ public final boolean playClearContentWithoutKey;
+
+ /** The types of clear tracks for which to use a drm session. */
+ public final List<Integer> sessionForClearTypes;
+
+ private DrmConfiguration(
+ UUID uuid,
+ @Nullable Uri licenseUri,
+ Map<String, String> requestHeaders,
+ boolean multiSession,
+ boolean playClearContentWithoutKey,
+ List<Integer> drmSessionForClearTypes) {
+ this.uuid = uuid;
+ this.licenseUri = licenseUri;
+ this.requestHeaders = requestHeaders;
+ this.multiSession = multiSession;
+ this.playClearContentWithoutKey = playClearContentWithoutKey;
+ this.sessionForClearTypes = drmSessionForClearTypes;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DrmConfiguration)) {
+ return false;
+ }
+
+ DrmConfiguration other = (DrmConfiguration) obj;
+ return uuid.equals(other.uuid)
+ && Util.areEqual(licenseUri, other.licenseUri)
+ && Util.areEqual(requestHeaders, other.requestHeaders)
+ && multiSession == other.multiSession
+ && playClearContentWithoutKey == other.playClearContentWithoutKey
+ && sessionForClearTypes.equals(other.sessionForClearTypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uuid.hashCode();
+ result = 31 * result + (licenseUri != null ? licenseUri.hashCode() : 0);
+ result = 31 * result + requestHeaders.hashCode();
+ result = 31 * result + (multiSession ? 1 : 0);
+ result = 31 * result + (playClearContentWithoutKey ? 1 : 0);
+ result = 31 * result + sessionForClearTypes.hashCode();
+ return result;
+ }
+ }
+
+ /** Properties for local playback. */
+ public static final class PlaybackProperties {
+
+ /** The source {@link Uri}. */
+ public final Uri sourceUri;
+
+ /**
+ * The optional mime type of the item, or {@code null} if unspecified.
+ *
+ * <p>The mime type can be used to disambiguate media items that have a uri which does not allow
+ * to infer the actual media type.
+ */
+ @Nullable public final String mimeType;
+
+ /** Optional {@link DrmConfiguration} for the media. */
+ @Nullable public final DrmConfiguration drmConfiguration;
+
+ /** Optional stream keys by which the manifest is filtered. */
+ public final List<StreamKey> streamKeys;
+
+ /** Optional subtitles to be sideloaded. */
+ public final List<Subtitle> subtitles;
+
+ /**
+ * Optional tag for custom attributes. The tag for the media source which will be published in
+ * the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
+ * com.google.android.exoplayer2.Timeline.Window#tag}.
+ */
+ @Nullable public final Object tag;
+
+ private PlaybackProperties(
+ Uri sourceUri,
+ @Nullable String mimeType,
+ @Nullable DrmConfiguration drmConfiguration,
+ List<StreamKey> streamKeys,
+ List<Subtitle> subtitles,
+ @Nullable Object tag) {
+ this.sourceUri = sourceUri;
+ this.mimeType = mimeType;
+ this.drmConfiguration = drmConfiguration;
+ this.streamKeys = streamKeys;
+ this.subtitles = subtitles;
+ this.tag = tag;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PlaybackProperties)) {
+ return false;
+ }
+ PlaybackProperties other = (PlaybackProperties) obj;
+
+ return sourceUri.equals(other.sourceUri)
+ && Util.areEqual(mimeType, other.mimeType)
+ && Util.areEqual(drmConfiguration, other.drmConfiguration)
+ && streamKeys.equals(other.streamKeys)
+ && subtitles.equals(other.subtitles)
+ && Util.areEqual(tag, other.tag);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = sourceUri.hashCode();
+ result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
+ result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode());
+ result = 31 * result + streamKeys.hashCode();
+ result = 31 * result + subtitles.hashCode();
+ result = 31 * result + (tag == null ? 0 : tag.hashCode());
+ return result;
+ }
+ }
+
+ /** Properties for a text track. */
+ public static final class Subtitle {
+
+ /** The {@link Uri} to the subtitle file. */
+ public final Uri uri;
+ /** The MIME type. */
+ public final String mimeType;
+ /** The language. */
+ @Nullable public final String language;
+ /** The selection flags. */
+ @C.SelectionFlags public final int selectionFlags;
+
+ /**
+ * Creates an instance.
+ *
+ * @param uri The {@link Uri uri} to the subtitle file.
+ * @param mimeType The mime type.
+ * @param language The optional language.
+ */
+ public Subtitle(Uri uri, String mimeType, @Nullable String language) {
+ this(uri, mimeType, language, /* selectionFlags= */ 0);
+ }
+
+ /**
+ * Creates an instance with the given selection flags.
+ *
+ * @param uri The {@link Uri uri} to the subtitle file.
+ * @param mimeType The mime type.
+ * @param language The optional language.
+ * @param selectionFlags The selection flags.
+ */
+ public Subtitle(
+ Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) {
+ this.uri = uri;
+ this.mimeType = mimeType;
+ this.language = language;
+ this.selectionFlags = selectionFlags;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Subtitle)) {
+ return false;
+ }
+
+ Subtitle other = (Subtitle) obj;
+
+ return uri.equals(other.uri)
+ && mimeType.equals(other.mimeType)
+ && Util.areEqual(language, other.language)
+ && selectionFlags == other.selectionFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uri.hashCode();
+ result = 31 * result + mimeType.hashCode();
+ result = 31 * result + (language == null ? 0 : language.hashCode());
+ result = 31 * result + selectionFlags;
+ return result;
+ }
+ }
+
+ /** Optionally clips the media item to a custom start and end position. */
+ public static final class ClippingProperties {
+
+ /** The start position in milliseconds. This is a value larger than or equal to zero. */
+ public final long startPositionMs;
+
+ /**
+ * The end position in milliseconds. This is a value larger than or equal to zero or {@link
+ * C#TIME_END_OF_SOURCE} to play to the end of the stream.
+ */
+ public final long endPositionMs;
+
+ /**
+ * Whether the clipping of active media periods moves with a live window. If {@code false},
+ * playback ends when it reaches {@link #endPositionMs}.
+ */
+ public final boolean relativeToLiveWindow;
+
+ /**
+ * Whether {@link #startPositionMs} and {@link #endPositionMs} are relative to the default
+ * position.
+ */
+ public final boolean relativeToDefaultPosition;
+
+ /** Sets whether the start point is guaranteed to be a key frame. */
+ public final boolean startsAtKeyFrame;
+
+ private ClippingProperties(
+ long startPositionMs,
+ long endPositionMs,
+ boolean relativeToLiveWindow,
+ boolean relativeToDefaultPosition,
+ boolean startsAtKeyFrame) {
+ this.startPositionMs = startPositionMs;
+ this.endPositionMs = endPositionMs;
+ this.relativeToLiveWindow = relativeToLiveWindow;
+ this.relativeToDefaultPosition = relativeToDefaultPosition;
+ this.startsAtKeyFrame = startsAtKeyFrame;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ClippingProperties)) {
+ return false;
+ }
+
+ ClippingProperties other = (ClippingProperties) obj;
+
+ return startPositionMs == other.startPositionMs
+ && endPositionMs == other.endPositionMs
+ && relativeToLiveWindow == other.relativeToLiveWindow
+ && relativeToDefaultPosition == other.relativeToDefaultPosition
+ && startsAtKeyFrame == other.startsAtKeyFrame;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Long.valueOf(startPositionMs).hashCode();
+ result = 31 * result + Long.valueOf(endPositionMs).hashCode();
+ result = 31 * result + (relativeToLiveWindow ? 1 : 0);
+ result = 31 * result + (relativeToDefaultPosition ? 1 : 0);
+ result = 31 * result + (startsAtKeyFrame ? 1 : 0);
+ return result;
+ }
+ }
+
+ /** Identifies the media item. */
+ public final String mediaId;
+
+ /** Optional playback properties. Maybe be {@code null} if shared over process boundaries. */
+ @Nullable public final PlaybackProperties playbackProperties;
+
+ /** The media metadata. */
+ public final MediaMetadata mediaMetadata;
+
+ /** The clipping properties. */
+ public final ClippingProperties clippingProperties;
+
+ private MediaItem(
+ String mediaId,
+ ClippingProperties clippingProperties,
+ @Nullable PlaybackProperties playbackProperties,
+ MediaMetadata mediaMetadata) {
+ this.mediaId = mediaId;
+ this.playbackProperties = playbackProperties;
+ this.mediaMetadata = mediaMetadata;
+ this.clippingProperties = clippingProperties;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MediaItem)) {
+ return false;
+ }
+
+ MediaItem other = (MediaItem) obj;
+
+ return Util.areEqual(mediaId, other.mediaId)
+ && clippingProperties.equals(other.clippingProperties)
+ && Util.areEqual(playbackProperties, other.playbackProperties)
+ && Util.areEqual(mediaMetadata, other.mediaMetadata);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mediaId.hashCode();
+ result = 31 * result + (playbackProperties != null ? playbackProperties.hashCode() : 0);
+ result = 31 * result + clippingProperties.hashCode();
+ result = 31 * result + mediaMetadata.hashCode();
+ return result;
+ }
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java
new file mode 100644
index 0000000..37fb8fc
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaMetadata.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.util.Util;
+
+/** Metadata of the {@link MediaItem}. */
+public final class MediaMetadata {
+
+ /** A builder for {@link MediaMetadata} instances. */
+ public static final class Builder {
+
+ @Nullable private String title;
+
+ /** Sets the optional title. */
+ public Builder setTitle(@Nullable String title) {
+ this.title = title;
+ return this;
+ }
+
+ /** Returns a new {@link MediaMetadata} instance with the current builder values. */
+ public MediaMetadata build() {
+ return new MediaMetadata(title);
+ }
+ }
+
+ /** Optional title. */
+ @Nullable public final String title;
+
+ private MediaMetadata(@Nullable String title) {
+ this.title = title;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ MediaMetadata other = (MediaMetadata) obj;
+
+ return Util.areEqual(title, other.title);
+ }
+
+ @Override
+ public int hashCode() {
+ return title == null ? 0 : title.hashCode();
+ }
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java
new file mode 100644
index 0000000..024f848
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/AacUtil.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.audio;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.ParsableBitArray;
+
+/** Utility methods for handling AAC audio streams. */
+public final class AacUtil {
+
+ private static final String TAG = "AacUtil";
+
+ /** Holds sample format information for AAC audio. */
+ public static final class Config {
+
+ /** The sample rate in Hertz. */
+ public final int sampleRateHz;
+ /** The number of channels. */
+ public final int channelCount;
+ /** The RFC 6381 codecs string. */
+ public final String codecs;
+
+ private Config(int sampleRateHz, int channelCount, String codecs) {
+ this.sampleRateHz = sampleRateHz;
+ this.channelCount = channelCount;
+ this.codecs = codecs;
+ }
+ }
+
+ // Audio sample count constants assume the frameLengthFlag in the access unit is 0.
+ /**
+ * Number of raw audio samples that are produced per channel when decoding an AAC LC access unit.
+ */
+ public static final int AAC_LC_AUDIO_SAMPLE_COUNT = 1024;
+ /**
+ * Number of raw audio samples that are produced per channel when decoding an AAC XHE access unit.
+ */
+ public static final int AAC_XHE_AUDIO_SAMPLE_COUNT = AAC_LC_AUDIO_SAMPLE_COUNT;
+ /**
+ * Number of raw audio samples that are produced per channel when decoding an AAC HE access unit.
+ */
+ public static final int AAC_HE_AUDIO_SAMPLE_COUNT = 2048;
+ /**
+ * Number of raw audio samples that are produced per channel when decoding an AAC LD access unit.
+ */
+ public static final int AAC_LD_AUDIO_SAMPLE_COUNT = 512;
+
+ // Maximum bitrates for AAC profiles from the Fraunhofer FDK AAC encoder documentation:
+ // https://cs.android.com/android/platform/superproject/+/android-9.0.0_r8:external/aac/libAACenc/include/aacenc_lib.h;l=718
+ /** Maximum rate for an AAC LC audio stream, in bytes per second. */
+ public static final int AAC_LC_MAX_RATE_BYTES_PER_SECOND = 800 * 1000 / 8;
+ /** Maximum rate for an AAC HE V1 audio stream, in bytes per second. */
+ public static final int AAC_HE_V1_MAX_RATE_BYTES_PER_SECOND = 128 * 1000 / 8;
+ /** Maximum rate for an AAC HE V2 audio stream, in bytes per second. */
+ public static final int AAC_HE_V2_MAX_RATE_BYTES_PER_SECOND = 56 * 1000 / 8;
+ /**
+ * Maximum rate for an AAC XHE audio stream, in bytes per second.
+ *
+ * <p>Fraunhofer documentation says "500 kbit/s and above" for stereo, so we use a rate generously
+ * above the 500 kbit/s level.
+ */
+ public static final int AAC_XHE_MAX_RATE_BYTES_PER_SECOND = 2048 * 1000 / 8;
+ /**
+ * Maximum rate for an AAC ELD audio stream, in bytes per second.
+ *
+ * <p>Fraunhofer documentation shows AAC-ELD as useful for up to ~ 64 kbit/s so we use this value.
+ */
+ public static final int AAC_ELD_MAX_RATE_BYTES_PER_SECOND = 64 * 1000 / 8;
+
+ private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
+ private static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE =
+ new int[] {
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
+ };
+ private static final int AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID = -1;
+ /**
+ * In the channel configurations below, <A> indicates a single channel element; (A, B)
+ * indicates a channel pair element; and [A] indicates a low-frequency effects element. The
+ * speaker mapping short forms used are:
+ *
+ * <ul>
+ * <li>FC: front center
+ * <li>BC: back center
+ * <li>FL/FR: front left/right
+ * <li>FCL/FCR: front center left/right
+ * <li>FTL/FTR: front top left/right
+ * <li>SL/SR: back surround left/right
+ * <li>BL/BR: back left/right
+ * <li>LFE: low frequency effects
+ * </ul>
+ */
+ private static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE =
+ new int[] {
+ 0,
+ 1, /* mono: <FC> */
+ 2, /* stereo: (FL, FR) */
+ 3, /* 3.0: <FC>, (FL, FR) */
+ 4, /* 4.0: <FC>, (FL, FR), <BC> */
+ 5, /* 5.0 back: <FC>, (FL, FR), (SL, SR) */
+ 6, /* 5.1 back: <FC>, (FL, FR), (SL, SR), <BC>, [LFE] */
+ 8, /* 7.1 wide back: <FC>, (FCL, FCR), (FL, FR), (SL, SR), [LFE] */
+ AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
+ AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
+ AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
+ 7, /* 6.1: <FC>, (FL, FR), (SL, SR), <RC>, [LFE] */
+ 8, /* 7.1: <FC>, (FL, FR), (SL, SR), (BL, BR), [LFE] */
+ AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
+ 8, /* 7.1 top: <FC>, (FL, FR), (SL, SR), [LFE], (FTL, FTR) */
+ AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID
+ };
+
+ /**
+ * Prefix for the RFC 6381 codecs string for AAC formats. To form a full codecs string, suffix the
+ * decimal AudioObjectType.
+ */
+ private static final String CODECS_STRING_PREFIX = "mp4a.40.";
+
+ // Advanced Audio Coding Low-Complexity profile.
+ private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2;
+ // Spectral Band Replication.
+ private static final int AUDIO_OBJECT_TYPE_AAC_SBR = 5;
+ // Error Resilient Bit-Sliced Arithmetic Coding.
+ private static final int AUDIO_OBJECT_TYPE_AAC_ER_BSAC = 22;
+ // Enhanced low delay.
+ private static final int AUDIO_OBJECT_TYPE_AAC_ELD = 23;
+ // Parametric Stereo.
+ private static final int AUDIO_OBJECT_TYPE_AAC_PS = 29;
+ // Escape code for extended audio object types.
+ private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31;
+ // Extended high efficiency.
+ private static final int AUDIO_OBJECT_TYPE_AAC_XHE = 42;
+
+ /**
+ * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
+ *
+ * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse.
+ * @return The parsed configuration.
+ * @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
+ */
+ public static Config parseAudioSpecificConfig(byte[] audioSpecificConfig) throws ParserException {
+ return parseAudioSpecificConfig(
+ new ParsableBitArray(audioSpecificConfig), /* forceReadToEnd= */ false);
+ }
+
+ /**
+ * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
+ *
+ * @param bitArray A {@link ParsableBitArray} containing the AudioSpecificConfig to parse. The
+ * position is advanced to the end of the AudioSpecificConfig.
+ * @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
+ * knowing the length of the configuration payload.
+ * @return The parsed configuration.
+ * @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
+ */
+ public static Config parseAudioSpecificConfig(ParsableBitArray bitArray, boolean forceReadToEnd)
+ throws ParserException {
+ int audioObjectType = getAudioObjectType(bitArray);
+ int sampleRateHz = getSamplingFrequency(bitArray);
+ int channelConfiguration = bitArray.readBits(4);
+ String codecs = CODECS_STRING_PREFIX + audioObjectType;
+ if (audioObjectType == AUDIO_OBJECT_TYPE_AAC_SBR
+ || audioObjectType == AUDIO_OBJECT_TYPE_AAC_PS) {
+ // For an AAC bitstream using spectral band replication (SBR) or parametric stereo (PS) with
+ // explicit signaling, we return the extension sampling frequency as the sample rate of the
+ // content; this is identical to the sample rate of the decoded output but may differ from
+ // the sample rate set above.
+ // Use the extensionSamplingFrequencyIndex.
+ sampleRateHz = getSamplingFrequency(bitArray);
+ audioObjectType = getAudioObjectType(bitArray);
+ if (audioObjectType == AUDIO_OBJECT_TYPE_AAC_ER_BSAC) {
+ // Use the extensionChannelConfiguration.
+ channelConfiguration = bitArray.readBits(4);
+ }
+ }
+
+ if (forceReadToEnd) {
+ switch (audioObjectType) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 6:
+ case 7:
+ case 17:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ parseGaSpecificConfig(bitArray, audioObjectType, channelConfiguration);
+ break;
+ default:
+ throw new ParserException("Unsupported audio object type: " + audioObjectType);
+ }
+ switch (audioObjectType) {
+ case 17:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ int epConfig = bitArray.readBits(2);
+ if (epConfig == 2 || epConfig == 3) {
+ throw new ParserException("Unsupported epConfig: " + epConfig);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ // For supported containers, bits_to_decode() is always 0.
+ int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration];
+ Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID);
+ return new Config(sampleRateHz, channelCount, codecs);
+ }
+
+ /**
+ * Builds a simple AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
+ *
+ * @param sampleRate The sample rate in Hz.
+ * @param channelCount The channel count.
+ * @return The AudioSpecificConfig.
+ */
+ public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int channelCount) {
+ int sampleRateIndex = C.INDEX_UNSET;
+ for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) {
+ if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) {
+ sampleRateIndex = i;
+ }
+ }
+ int channelConfig = C.INDEX_UNSET;
+ for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) {
+ if (channelCount == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) {
+ channelConfig = i;
+ }
+ }
+ if (sampleRate == C.INDEX_UNSET || channelConfig == C.INDEX_UNSET) {
+ throw new IllegalArgumentException(
+ "Invalid sample rate or number of channels: " + sampleRate + ", " + channelCount);
+ }
+ return buildAudioSpecificConfig(AUDIO_OBJECT_TYPE_AAC_LC, sampleRateIndex, channelConfig);
+ }
+
+ /**
+ * Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
+ *
+ * @param audioObjectType The audio object type.
+ * @param sampleRateIndex The sample rate index.
+ * @param channelConfig The channel configuration.
+ * @return The AudioSpecificConfig.
+ */
+ public static byte[] buildAudioSpecificConfig(
+ int audioObjectType, int sampleRateIndex, int channelConfig) {
+ byte[] specificConfig = new byte[2];
+ specificConfig[0] = (byte) (((audioObjectType << 3) & 0xF8) | ((sampleRateIndex >> 1) & 0x07));
+ specificConfig[1] = (byte) (((sampleRateIndex << 7) & 0x80) | ((channelConfig << 3) & 0x78));
+ return specificConfig;
+ }
+
+ /** Returns the encoding for a given AAC audio object type. */
+ @C.Encoding
+ public static int getEncodingForAudioObjectType(int audioObjectType) {
+ switch (audioObjectType) {
+ case AUDIO_OBJECT_TYPE_AAC_LC:
+ return C.ENCODING_AAC_LC;
+ case AUDIO_OBJECT_TYPE_AAC_SBR:
+ return C.ENCODING_AAC_HE_V1;
+ case AUDIO_OBJECT_TYPE_AAC_PS:
+ return C.ENCODING_AAC_HE_V2;
+ case AUDIO_OBJECT_TYPE_AAC_XHE:
+ return C.ENCODING_AAC_XHE;
+ case AUDIO_OBJECT_TYPE_AAC_ELD:
+ return C.ENCODING_AAC_ELD;
+ default:
+ return C.ENCODING_INVALID;
+ }
+ }
+
+ /**
+ * Returns the AAC audio object type as specified in 14496-3 (2005) Table 1.14.
+ *
+ * @param bitArray The bit array containing the audio specific configuration.
+ * @return The audio object type.
+ */
+ private static int getAudioObjectType(ParsableBitArray bitArray) {
+ int audioObjectType = bitArray.readBits(5);
+ if (audioObjectType == AUDIO_OBJECT_TYPE_ESCAPE) {
+ audioObjectType = 32 + bitArray.readBits(6);
+ }
+ return audioObjectType;
+ }
+
+ /**
+ * Returns the AAC sampling frequency (or extension sampling frequency) as specified in 14496-3
+ * (2005) Table 1.13.
+ *
+ * @param bitArray The bit array containing the audio specific configuration.
+ * @return The sampling frequency.
+ */
+ private static int getSamplingFrequency(ParsableBitArray bitArray) {
+ int samplingFrequency;
+ int frequencyIndex = bitArray.readBits(4);
+ if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) {
+ samplingFrequency = bitArray.readBits(24);
+ } else {
+ Assertions.checkArgument(frequencyIndex < 13);
+ samplingFrequency = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex];
+ }
+ return samplingFrequency;
+ }
+
+ private static void parseGaSpecificConfig(
+ ParsableBitArray bitArray, int audioObjectType, int channelConfiguration) {
+ boolean frameLengthFlag = bitArray.readBit();
+ if (frameLengthFlag) {
+ Log.w(TAG, "Unexpected frameLengthFlag = 1");
+ }
+ boolean dependsOnCoreDecoder = bitArray.readBit();
+ if (dependsOnCoreDecoder) {
+ bitArray.skipBits(14); // coreCoderDelay.
+ }
+ boolean extensionFlag = bitArray.readBit();
+ if (channelConfiguration == 0) {
+ throw new UnsupportedOperationException(); // TODO: Implement programConfigElement();
+ }
+ if (audioObjectType == 6 || audioObjectType == 20) {
+ bitArray.skipBits(3); // layerNr.
+ }
+ if (extensionFlag) {
+ if (audioObjectType == 22) {
+ bitArray.skipBits(16); // numOfSubFrame (5), layer_length(11).
+ }
+ if (audioObjectType == 17
+ || audioObjectType == 19
+ || audioObjectType == 20
+ || audioObjectType == 23) {
+ // aacSectionDataResilienceFlag, aacScalefactorDataResilienceFlag,
+ // aacSpectralDataResilienceFlag.
+ bitArray.skipBits(3);
+ }
+ bitArray.skipBits(1); // extensionFlag3.
+ }
+ }
+
+ private AacUtil() {}
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java
index 53803ad..d4042a9 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java
@@ -99,6 +99,13 @@
}
+ /** Maximum rate for an AC-3 audio stream, in bytes per second. */
+ public static final int AC3_MAX_RATE_BYTES_PER_SECOND = 640 * 1000 / 8;
+ /** Maximum rate for an E-AC-3 audio stream, in bytes per second. */
+ public static final int E_AC3_MAX_RATE_BYTES_PER_SECOND = 6144 * 1000 / 8;
+ /** Maximum rate for a TrueHD audio stream, in bytes per second. */
+ public static final int TRUEHD_MAX_RATE_BYTES_PER_SECOND = 24500 * 1000 / 8;
+
/**
* The number of samples to store in each output chunk when rechunking TrueHD streams. The number
* of samples extracted from the container corresponding to one syncframe must be an integer
@@ -163,18 +170,14 @@
if ((nextByte & 0x04) != 0) { // lfeon
channelCount++;
}
- return Format.createAudioSampleFormat(
- trackId,
- MimeTypes.AUDIO_AC3,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- sampleRate,
- /* initializationData= */ null,
- drmInitData,
- /* selectionFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(MimeTypes.AUDIO_AC3)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
}
/**
@@ -218,18 +221,14 @@
mimeType = MimeTypes.AUDIO_E_AC3_JOC;
}
}
- return Format.createAudioSampleFormat(
- trackId,
- mimeType,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- sampleRate,
- /* initializationData= */ null,
- drmInitData,
- /* selectionFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
}
/**
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java
index b9f1dc5..2e4367f 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java
@@ -54,6 +54,9 @@
public static final int AC40_SYNCWORD = 0xAC40;
public static final int AC41_SYNCWORD = 0xAC41;
+ /** Maximum rate for an AC-4 audio stream, in bytes per second. */
+ public static final int MAX_RATE_BYTES_PER_SECOND = 2688 * 1000 / 8;
+
/** The channel count of AC-4 stream. */
// TODO: Parse AC-4 stream channel count.
private static final int CHANNEL_COUNT_2 = 2;
@@ -104,18 +107,14 @@
ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) {
data.skipBytes(1); // ac4_dsi_version, bitstream_version[0:5]
int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
- return Format.createAudioSampleFormat(
- trackId,
- MimeTypes.AUDIO_AC4,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- CHANNEL_COUNT_2,
- sampleRate,
- /* initializationData= */ null,
- drmInitData,
- /* selectionFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(MimeTypes.AUDIO_AC4)
+ .setChannelCount(CHANNEL_COUNT_2)
+ .setSampleRate(sampleRate)
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
}
/**
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java
index f57d3b2..8640c46 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java
@@ -28,6 +28,15 @@
*/
public final class DtsUtil {
+ /**
+ * Maximum rate for a DTS audio stream, in bytes per second.
+ *
+ * <p>DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
+ */
+ public static final int DTS_MAX_RATE_BYTES_PER_SECOND = 1536 * 1000 / 8;
+ /** Maximum rate for a DTS-HD audio stream, in bytes per second. */
+ public static final int DTS_HD_MAX_RATE_BYTES_PER_SECOND = 18000 * 1000 / 8;
+
private static final int SYNC_VALUE_BE = 0x7FFE8001;
private static final int SYNC_VALUE_14B_BE = 0x1FFFE800;
private static final int SYNC_VALUE_LE = 0xFE7F0180;
@@ -96,8 +105,15 @@
: TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2;
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
- return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, null, bitrate,
- Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language);
+ return new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(MimeTypes.AUDIO_DTS)
+ .setAverageBitrate(bitrate)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
}
/**
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java
index 36a5a8d..d09443d 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java
@@ -41,9 +41,9 @@
public int samplesPerFrame;
/**
- * Populates the fields in this instance to reflect the the MPEG audio header in {@code
- * headerData}, returning whether the header was valid. If false, the values of the fields in
- * this instance will not be updated.
+ * Populates the fields in this instance to reflect the MPEG audio header in {@code headerData},
+ * returning whether the header was valid. If false, the values of the fields in this instance
+ * will not be updated.
*
* @param headerData Header data to parse.
* @return True if the fields were populated. False otherwise, indicating that {@code
@@ -209,6 +209,12 @@
*/
public static final int MAX_FRAME_SIZE_BYTES = 4096;
+ /**
+ * Maximum rate for an MPEG audio stream corresponding to MPEG-1 layer III (320 kbit/s), in bytes
+ * per second.
+ */
+ public static final int MAX_RATE_BYTES_PER_SECOND = 320 * 1000 / 8;
+
private static final String[] MIME_TYPE_BY_LAYER =
new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG};
private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/WavUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/WavUtil.java
index dff8102..2089891 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/WavUtil.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/audio/WavUtil.java
@@ -61,6 +61,7 @@
return TYPE_PCM;
case C.ENCODING_PCM_FLOAT:
return TYPE_FLOAT;
+ case C.ENCODING_PCM_16BIT_BIG_ENDIAN: // Not TYPE_PCM, because TYPE_PCM is little endian.
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java
index 379ca97..1c52abc 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java
@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer2.decoder;
-import android.annotation.TargetApi;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
@@ -25,27 +25,41 @@
public final class CryptoInfo {
/**
+ * The 16 byte initialization vector. If the initialization vector of the content is shorter than
+ * 16 bytes, 0 byte padding is appended to extend the vector to the required 16 byte length.
+ *
* @see android.media.MediaCodec.CryptoInfo#iv
*/
public byte[] iv;
/**
+ * The 16 byte key id.
+ *
* @see android.media.MediaCodec.CryptoInfo#key
*/
public byte[] key;
/**
+ * The type of encryption that has been applied. Must be one of the {@link C.CryptoMode} values.
+ *
* @see android.media.MediaCodec.CryptoInfo#mode
*/
- @C.CryptoMode
- public int mode;
+ @C.CryptoMode public int mode;
/**
+ * The number of leading unencrypted bytes in each sub-sample. If null, all bytes are treated as
+ * encrypted and {@link #numBytesOfEncryptedData} must be specified.
+ *
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
*/
public int[] numBytesOfClearData;
/**
+ * The number of trailing encrypted bytes in each sub-sample. If null, all bytes are treated as
+ * clear and {@link #numBytesOfClearData} must be specified.
+ *
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
*/
public int[] numBytesOfEncryptedData;
/**
+ * The number of subSamples that make up the buffer's contents.
+ *
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
@@ -111,7 +125,30 @@
return getFrameworkCryptoInfo();
}
- @TargetApi(24)
+ /**
+ * Increases the number of clear data for the first sub sample by {@code count}.
+ *
+ * <p>If {@code count} is 0, this method is a no-op. Otherwise, it adds {@code count} to {@link
+ * #numBytesOfClearData}[0].
+ *
+ * <p>If {@link #numBytesOfClearData} is null (which is permitted), this method will instantiate
+ * it to a new {@code int[1]}.
+ *
+ * @param count The number of bytes to be added to the first subSample of {@link
+ * #numBytesOfClearData}.
+ */
+ public void increaseClearDataFirstSubSampleBy(int count) {
+ if (count == 0) {
+ return;
+ }
+ if (numBytesOfClearData == null) {
+ numBytesOfClearData = new int[1];
+ frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
+ }
+ numBytesOfClearData[0] += count;
+ }
+
+ @RequiresApi(24)
private static final class PatternHolderV24 {
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java
index 302ae06..bd5df4c 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java
@@ -63,6 +63,14 @@
/** The buffer's data, or {@code null} if no data has been set. */
@Nullable public ByteBuffer data;
+ // TODO: Remove this temporary signaling once end-of-stream propagation for clips using content
+ // protection is fixed. See [Internal: b/153326944] for details.
+ /**
+ * Whether the last attempt to read a sample into this buffer failed due to not yet having the DRM
+ * keys associated with the next sample.
+ */
+ public boolean waitingForKeys;
+
/**
* The time at which the sample should be presented.
*/
@@ -137,6 +145,7 @@
}
// Instantiate a new buffer if possible.
ByteBuffer newData = createReplacementByteBuffer(requiredCapacity);
+ newData.order(data.order());
// Copy data up to the current position from the old buffer to the new one.
if (position > 0) {
data.flip();
@@ -182,6 +191,7 @@
if (supplementalData != null) {
supplementalData.clear();
}
+ waitingForKeys = false;
}
private ByteBuffer createReplacementByteBuffer(int requiredCapacity) {
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java
index 2f0246b..ee09838 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java
@@ -285,7 +285,7 @@
* The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e.
* applies to all schemes).
*/
- private final UUID uuid;
+ public final UUID uuid;
/** The URL of the server to which license requests should be made. May be null if unknown. */
@Nullable public final String licenseServerUrl;
/** The mimeType of {@link #data}. */
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java
deleted file mode 100644
index 35358f0..0000000
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.drm;
-
-import android.media.MediaDrm;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import java.io.IOException;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
-
-/**
- * A DRM session.
- */
-public interface DrmSession<T extends ExoMediaCrypto> {
-
- /**
- * Invokes {@code newSession's} {@link #acquire()} and {@code previousSession's} {@link
- * #release()} in that order. Null arguments are ignored. Does nothing if {@code previousSession}
- * and {@code newSession} are the same session.
- */
- static <T extends ExoMediaCrypto> void replaceSession(
- @Nullable DrmSession<T> previousSession, @Nullable DrmSession<T> newSession) {
- if (previousSession == newSession) {
- // Do nothing.
- return;
- }
- if (newSession != null) {
- newSession.acquire();
- }
- if (previousSession != null) {
- previousSession.release();
- }
- }
-
- /** Wraps the throwable which is the cause of the error state. */
- class DrmSessionException extends IOException {
-
- public DrmSessionException(Throwable cause) {
- super(cause);
- }
-
- }
-
- /**
- * The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link
- * #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}.
- */
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
- @interface State {}
- /**
- * The session has been released.
- */
- int STATE_RELEASED = 0;
- /**
- * The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
- */
- int STATE_ERROR = 1;
- /**
- * The session is being opened.
- */
- int STATE_OPENING = 2;
- /** The session is open, but does not have keys required for decryption. */
- int STATE_OPENED = 3;
- /** The session is open and has keys required for decryption. */
- int STATE_OPENED_WITH_KEYS = 4;
-
- /**
- * Returns the current state of the session, which is one of {@link #STATE_ERROR},
- * {@link #STATE_RELEASED}, {@link #STATE_OPENING}, {@link #STATE_OPENED} and
- * {@link #STATE_OPENED_WITH_KEYS}.
- */
- @State int getState();
-
- /** Returns whether this session allows playback of clear samples prior to keys being loaded. */
- default boolean playClearSamplesWithoutKeys() {
- return false;
- }
-
- /**
- * Returns the cause of the error state, or null if {@link #getState()} is not {@link
- * #STATE_ERROR}.
- */
- @Nullable
- DrmSessionException getError();
-
- /**
- * Returns a {@link ExoMediaCrypto} for the open session, or null if called before the session has
- * been opened or after it's been released.
- */
- @Nullable
- T getMediaCrypto();
-
- /**
- * Returns a map describing the key status for the session, or null if called before the session
- * has been opened or after it's been released.
- *
- * <p>Since DRM license policies vary by vendor, the specific status field names are determined by
- * each DRM vendor. Refer to your DRM provider documentation for definitions of the field names
- * for a particular DRM engine plugin.
- *
- * @return A map describing the key status for the session, or null if called before the session
- * has been opened or after it's been released.
- * @see MediaDrm#queryKeyStatus(byte[])
- */
- @Nullable
- Map<String, String> queryKeyStatus();
-
- /**
- * Returns the key set id of the offline license loaded into this session, or null if there isn't
- * one.
- */
- @Nullable
- byte[] getOfflineLicenseKeySetId();
-
- /**
- * Increments the reference count. When the caller no longer needs to use the instance, it must
- * call {@link #release()} to decrement the reference count.
- */
- void acquire();
-
- /**
- * Decrements the reference count. If the reference count drops to 0 underlying resources are
- * released, and the instance cannot be re-used.
- */
- void release();
-}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
index d735c9d..dee0db5 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java
@@ -27,7 +27,8 @@
* Decodes a {@link Metadata} element from the provided input buffer.
*
* <p>Respects {@link ByteBuffer#limit()} of {@code inputBuffer.data}, but assumes {@link
- * ByteBuffer#position()} and {@link ByteBuffer#arrayOffset()} are both zero.
+ * ByteBuffer#position()} and {@link ByteBuffer#arrayOffset()} are both zero and {@link
+ * ByteBuffer#hasArray()} is true.
*
* @param inputBuffer The input buffer to decode.
* @return The decoded metadata object, or null if the metadata could not be decoded.
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java
index 7e3862c..0dd46ba 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java
@@ -50,11 +50,9 @@
@VisibleForTesting public static final String SCTE35_SCHEME_ID = "urn:scte:scte35:2014:bin";
private static final Format ID3_FORMAT =
- Format.createSampleFormat(
- /* id= */ null, MimeTypes.APPLICATION_ID3, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_ID3).build();
private static final Format SCTE35_FORMAT =
- Format.createSampleFormat(
- /* id= */ null, MimeTypes.APPLICATION_SCTE35, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_SCTE35).build();
/** The message scheme. */
public final String schemeIdUri;
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java
index d87376f..c03a5cb 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java
@@ -26,13 +26,12 @@
/** Decodes data encoded by {@link EventMessageEncoder}. */
public final class EventMessageDecoder implements MetadataDecoder {
- @SuppressWarnings("ByteBufferBackingArray")
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
- byte[] data = buffer.array();
- int size = buffer.limit();
- return new Metadata(decode(new ParsableByteArray(data, size)));
+ Assertions.checkArgument(
+ buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
+ return new Metadata(decode(new ParsableByteArray(buffer.array(), buffer.limit())));
}
public EventMessage decode(ParsableByteArray emsgData) {
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
index faab7f0..84a316e 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java
@@ -96,11 +96,12 @@
this.framePredicate = framePredicate;
}
- @SuppressWarnings("ByteBufferBackingArray")
@Override
@Nullable
public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
+ Assertions.checkArgument(
+ buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
return decode(buffer.array(), buffer.limit());
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java
similarity index 100%
rename from tree/library/core/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java
rename to tree/library/common/src/main/java/com/google/android/exoplayer2/offline/StreamKey.java
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/package-info.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/offline/package-info.java
similarity index 100%
rename from tree/library/core/src/main/java/com/google/android/exoplayer2/offline/package-info.java
rename to tree/library/common/src/main/java/com/google/android/exoplayer2/offline/package-info.java
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataReader.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataReader.java
new file mode 100644
index 0000000..eeddc99
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataReader.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.upstream;
+
+import com.google.android.exoplayer2.C;
+import java.io.IOException;
+
+/** Reads bytes from a data stream. */
+public interface DataReader {
+ /**
+ * Reads up to {@code length} bytes of data from the input.
+ *
+ * <p>If {@code readLength} is zero then 0 is returned. Otherwise, if no data is available because
+ * the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned.
+ * Otherwise, the call will block until at least one byte of data has been read and the number of
+ * bytes read is returned.
+ *
+ * @param target A target array into which data should be written.
+ * @param offset The offset into the target array at which to write.
+ * @param length The maximum number of bytes to read from the input.
+ * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the input has ended. This
+ * may be less than {@code length} because the end of the input (or available data) was
+ * reached, the method was interrupted, or the operation was aborted early for another reason.
+ * @throws IOException If an error occurs reading from the input.
+ */
+ int read(byte[] target, int offset, int length) throws IOException;
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
index 204b9d4..9a321fb 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
@@ -23,10 +23,8 @@
import java.util.List;
import java.util.Map;
-/**
- * A component from which streams of data can be read.
- */
-public interface DataSource {
+/** Reads data from URI-identified resources. */
+public interface DataSource extends DataReader {
/**
* A factory for {@link DataSource} instances.
@@ -64,24 +62,6 @@
long open(DataSpec dataSpec) throws IOException;
/**
- * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, starting at
- * index {@code offset}.
- *
- * <p>If {@code readLength} is zero then 0 is returned. Otherwise, if no data is available because
- * the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned.
- * Otherwise, the call will block until at least one byte of data has been read and the number of
- * bytes read is returned.
- *
- * @param buffer The buffer into which the read data should be stored.
- * @param offset The start offset into {@code buffer} at which data should be written.
- * @param readLength The maximum number of bytes to read.
- * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is available
- * because the end of the opened range has been reached.
- * @throws IOException If an error occurs reading from the source.
- */
- int read(byte[] buffer, int offset, int readLength) throws IOException;
-
- /**
* When the source is open, returns the {@link Uri} from which data is being read. The returned
* {@link Uri} will be identical to the one passed {@link #open(DataSpec)} in the {@link DataSpec}
* unless redirection has occurred. If redirection has occurred, the {@link Uri} after redirection
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
index c090c2f..cdbf3fe 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java
@@ -23,7 +23,6 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +33,196 @@
public final class DataSpec {
/**
+ * Builds {@link DataSpec} instances.
+ *
+ * <p>Use DataSpec#buildUpon() to obtain a builder representing an existing {@link DataSpec}.
+ */
+ public static final class Builder {
+
+ @Nullable private Uri uri;
+ private long uriPositionOffset;
+ @HttpMethod private int httpMethod;
+ @Nullable private byte[] httpBody;
+ private Map<String, String> httpRequestHeaders;
+ private long position;
+ private long length;
+ @Nullable private String key;
+ @Flags private int flags;
+ @Nullable private Object customData;
+
+ /** Creates a new instance with default values. */
+ public Builder() {
+ httpMethod = HTTP_METHOD_GET;
+ httpRequestHeaders = Collections.emptyMap();
+ length = C.LENGTH_UNSET;
+ }
+
+ /**
+ * Creates a new instance to build upon the provided {@link DataSpec}.
+ *
+ * @param dataSpec The {@link DataSpec} to build upon.
+ */
+ private Builder(DataSpec dataSpec) {
+ uri = dataSpec.uri;
+ uriPositionOffset = dataSpec.uriPositionOffset;
+ httpMethod = dataSpec.httpMethod;
+ httpBody = dataSpec.httpBody;
+ httpRequestHeaders = dataSpec.httpRequestHeaders;
+ position = dataSpec.position;
+ length = dataSpec.length;
+ key = dataSpec.key;
+ flags = dataSpec.flags;
+ customData = dataSpec.customData;
+ }
+
+ /**
+ * Sets {@link DataSpec#uri}.
+ *
+ * @param uriString The {@link DataSpec#uri}.
+ * @return The builder.
+ */
+ public Builder setUri(String uriString) {
+ this.uri = Uri.parse(uriString);
+ return this;
+ }
+
+ /**
+ * Sets {@link DataSpec#uri}.
+ *
+ * @param uri The {@link DataSpec#uri}.
+ * @return The builder.
+ */
+ public Builder setUri(Uri uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#uriPositionOffset}. The default value is 0.
+ *
+ * @param uriPositionOffset The {@link DataSpec#uriPositionOffset}.
+ * @return The builder.
+ */
+ public Builder setUriPositionOffset(long uriPositionOffset) {
+ this.uriPositionOffset = uriPositionOffset;
+ return this;
+ }
+
+ /**
+ * Sets {@link DataSpec#httpMethod}. The default value is {@link #HTTP_METHOD_GET}.
+ *
+ * @param httpMethod The {@link DataSpec#httpMethod}.
+ * @return The builder.
+ */
+ public Builder setHttpMethod(@HttpMethod int httpMethod) {
+ this.httpMethod = httpMethod;
+ return this;
+ }
+
+ /**
+ * Sets {@link DataSpec#httpBody}. The default value is {@code null}.
+ *
+ * @param httpBody The {@link DataSpec#httpBody}.
+ * @return The builder.
+ */
+ public Builder setHttpBody(@Nullable byte[] httpBody) {
+ this.httpBody = httpBody;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#httpRequestHeaders}. The default value is an empty map.
+ *
+ * <p>Note: {@code Range}, {@code Accept-Encoding} and {@code User-Agent} should not be set with
+ * this method, since they are set directly by {@link HttpDataSource} implementations. See
+ * {@link DataSpec#httpRequestHeaders} for more details.
+ *
+ * @param httpRequestHeaders The {@link DataSpec#httpRequestHeaders}.
+ * @return The builder.
+ */
+ public Builder setHttpRequestHeaders(Map<String, String> httpRequestHeaders) {
+ this.httpRequestHeaders = httpRequestHeaders;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#position}. The default value is 0.
+ *
+ * @param position The {@link DataSpec#position}.
+ * @return The builder.
+ */
+ public Builder setPosition(long position) {
+ this.position = position;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#length}. The default value is {@link C#LENGTH_UNSET}.
+ *
+ * @param length The {@link DataSpec#length}.
+ * @return The builder.
+ */
+ public Builder setLength(long length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#key}. The default value is {@code null}.
+ *
+ * @param key The {@link DataSpec#key}.
+ * @return The builder.
+ */
+ public Builder setKey(@Nullable String key) {
+ this.key = key;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#flags}. The default value is 0.
+ *
+ * @param flags The {@link DataSpec#flags}.
+ * @return The builder.
+ */
+ public Builder setFlags(@Flags int flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ /**
+ * Sets the {@link DataSpec#customData}. The default value is {@code null}.
+ *
+ * @param customData The {@link DataSpec#customData}.
+ * @return The builder.
+ */
+ public Builder setCustomData(@Nullable Object customData) {
+ this.customData = customData;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DataSpec} with the builder's current values.
+ *
+ * @return The build {@link DataSpec}.
+ * @throws IllegalStateException If {@link #setUri} has not been called.
+ */
+ public DataSpec build() {
+ Assertions.checkStateNotNull(uri, "The uri must be set.");
+ return new DataSpec(
+ uri,
+ uriPositionOffset,
+ httpMethod,
+ httpBody,
+ httpRequestHeaders,
+ position,
+ length,
+ key,
+ flags,
+ customData);
+ }
+ }
+
+ /**
* The flags that apply to any request for data. Possible flag values are {@link
* #FLAG_ALLOW_GZIP}, {@link #FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}, {@link
* #FLAG_ALLOW_CACHE_FRAGMENTATION}, and {@link #FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED}.
@@ -77,128 +266,226 @@
public static final int FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED = 1 << 3;
/**
- * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link
- * #HTTP_METHOD_GET}, {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}.
+ * HTTP methods supported by ExoPlayer {@link HttpDataSource}s. One of {@link #HTTP_METHOD_GET},
+ * {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD})
public @interface HttpMethod {}
-
+ /** HTTP GET method. */
public static final int HTTP_METHOD_GET = 1;
+ /** HTTP POST method. */
public static final int HTTP_METHOD_POST = 2;
+ /** HTTP HEAD method. */
public static final int HTTP_METHOD_HEAD = 3;
/**
- * The source from which data should be read.
+ * Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the given
+ * {@link HttpMethod}.
*/
+ public static String getStringForHttpMethod(@HttpMethod int httpMethod) {
+ switch (httpMethod) {
+ case HTTP_METHOD_GET:
+ return "GET";
+ case HTTP_METHOD_POST:
+ return "POST";
+ case HTTP_METHOD_HEAD:
+ return "HEAD";
+ default:
+ // Never happens.
+ throw new IllegalStateException();
+ }
+ }
+
+ /** The {@link Uri} from which data should be read. */
public final Uri uri;
/**
- * The HTTP method, which will be used by {@link HttpDataSource} when requesting this DataSpec.
- * This value will be ignored by non-http {@link DataSource}s.
+ * The offset of the data located at {@link #uri} within an original resource.
+ *
+ * <p>Equal to 0 unless {@link #uri} provides access to a subset of an original resource. As an
+ * example, consider a resource that can be requested over the network and is 1000 bytes long. If
+ * {@link #uri} points to a local file that contains just bytes [200-300], then this field will be
+ * set to {@code 200}.
+ *
+ * <p>This field can be ignored except for in specific circumstances where the absolute position
+ * in the original resource is required in a {@link DataSource} chain. One example is when a
+ * {@link DataSource} needs to decrypt the content as it's read. In this case the absolute
+ * position in the original resource is typically needed to correctly initialize the decryption
+ * algorithm.
*/
- public final @HttpMethod int httpMethod;
+ public final long uriPositionOffset;
/**
- * The HTTP request body, null otherwise. If the body is non-null, then httpBody.length will be
- * non-zero.
+ * The HTTP method to use when requesting the data. This value will be ignored by non-HTTP {@link
+ * DataSource} implementations.
+ */
+ @HttpMethod public final int httpMethod;
+
+ /**
+ * The HTTP request body, null otherwise. If the body is non-null, then {@code httpBody.length}
+ * will be non-zero.
*/
@Nullable public final byte[] httpBody;
- /** Immutable map containing the headers to use in HTTP requests. */
+ /**
+ * Additional HTTP headers to use when requesting the data.
+ *
+ * <p>Note: This map is for additional headers specific to the data being requested. It does not
+ * include headers that are set directly by {@link HttpDataSource} implementations. In particular,
+ * this means the following headers are not included:
+ *
+ * <ul>
+ * <li>{@code Range}: {@link HttpDataSource} implementations derive the {@code Range} header
+ * from {@link #position} and {@link #length}.
+ * <li>{@code Accept-Encoding}: {@link HttpDataSource} implementations derive the {@code
+ * Accept-Encoding} header based on whether {@link #flags} includes {@link
+ * #FLAG_ALLOW_GZIP}.
+ * <li>{@code User-Agent}: {@link HttpDataSource} implementations set the {@code User-Agent}
+ * header directly.
+ * <li>Other headers set at the {@link HttpDataSource} layer. I.e., headers set using {@link
+ * HttpDataSource#setRequestProperty(String, String)}, and using {@link
+ * HttpDataSource.RequestProperties#set(String, String)} on the default properties obtained
+ * from {@link HttpDataSource.Factory#getDefaultRequestProperties()}.
+ * </ul>
+ */
public final Map<String, String> httpRequestHeaders;
- /** The absolute position of the data in the full stream. */
- public final long absoluteStreamPosition;
/**
- * The position of the data when read from {@link #uri}.
- * <p>
- * Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location
- * of a subset of the underlying data.
+ * The absolute position of the data in the full stream.
+ *
+ * @deprecated Use {@link #position} except for specific use cases where the absolute position
+ * within the original resource is required within a {@link DataSource} chain. Where the
+ * absolute position is required, use {@code uriPositionOffset + position}.
*/
+ @Deprecated public final long absoluteStreamPosition;
+
+ /** The position of the data when read from {@link #uri}. */
public final long position;
+
/**
* The length of the data, or {@link C#LENGTH_UNSET}.
*/
public final long length;
+
/**
* A key that uniquely identifies the original stream. Used for cache indexing. May be null if the
* data spec is not intended to be used in conjunction with a cache.
*/
@Nullable public final String key;
+
/** Request {@link Flags flags}. */
- public final @Flags int flags;
+ @Flags public final int flags;
/**
- * Construct a data spec for the given uri and with {@link #key} set to null.
+ * Application specific data.
+ *
+ * <p>This field is intended for advanced use cases in which applications require the ability to
+ * attach custom data to {@link DataSpec} instances. The custom data should be immutable.
+ */
+ @Nullable public final Object customData;
+
+ /**
+ * Constructs an instance.
*
* @param uri {@link #uri}.
*/
public DataSpec(Uri uri) {
- this(uri, 0);
+ this(uri, /* position= */ 0, /* length= */ C.LENGTH_UNSET);
}
/**
- * Construct a data spec for the given uri and with {@link #key} set to null.
+ * Constructs an instance.
*
* @param uri {@link #uri}.
+ * @param position {@link #position}.
+ * @param length {@link #length}.
+ */
+ public DataSpec(Uri uri, long position, long length) {
+ this(
+ uri,
+ /* uriPositionOffset= */ 0,
+ HTTP_METHOD_GET,
+ /* httpBody= */ null,
+ /* httpRequestHeaders= */ Collections.emptyMap(),
+ position,
+ length,
+ /* key= */ null,
+ /* flags= */ 0,
+ /* customData= */ null);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @deprecated Use {@link Builder}.
+ * @param uri {@link #uri}.
* @param flags {@link #flags}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public DataSpec(Uri uri, @Flags int flags) {
- this(uri, 0, C.LENGTH_UNSET, null, flags);
+ this(uri, /* position= */ 0, C.LENGTH_UNSET, /* key= */ null, flags);
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition}.
+ * Constructs an instance.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
+ * @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
*/
- public DataSpec(Uri uri, long absoluteStreamPosition, long length, @Nullable String key) {
- this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, 0);
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ public DataSpec(Uri uri, long position, long length, @Nullable String key) {
+ this(uri, position, position, length, key, /* flags= */ 0);
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition}.
+ * Constructs an instance.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
+ * @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
*/
- public DataSpec(
- Uri uri, long absoluteStreamPosition, long length, @Nullable String key, @Flags int flags) {
- this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags);
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ public DataSpec(Uri uri, long position, long length, @Nullable String key, @Flags int flags) {
+ this(uri, position, position, length, key, flags);
}
/**
- * Construct a data spec where {@link #position} equals {@link #absoluteStreamPosition} and has
- * request headers.
+ * Constructs an instance.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
+ * @param position {@link #position}, equal to {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
* @param httpRequestHeaders {@link #httpRequestHeaders}
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public DataSpec(
Uri uri,
- long absoluteStreamPosition,
+ long position,
long length,
@Nullable String key,
@Flags int flags,
Map<String, String> httpRequestHeaders) {
this(
uri,
- inferHttpMethod(null),
- null,
- absoluteStreamPosition,
- absoluteStreamPosition,
+ HTTP_METHOD_GET,
+ /* httpBody= */ null,
+ position,
+ position,
length,
key,
flags,
@@ -206,15 +493,18 @@
}
/**
- * Construct a data spec where {@link #position} may differ from {@link #absoluteStreamPosition}.
+ * Constructs an instance where {@link #uriPositionOffset} may be non-zero.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public DataSpec(
Uri uri,
long absoluteStreamPosition,
@@ -222,23 +512,27 @@
long length,
@Nullable String key,
@Flags int flags) {
- this(uri, null, absoluteStreamPosition, position, length, key, flags);
+ this(uri, /* postBody= */ null, absoluteStreamPosition, position, length, key, flags);
}
/**
- * Construct a data spec by inferring the {@link #httpMethod} based on the {@code postBody}
- * parameter. If postBody is non-null, then httpMethod is set to {@link #HTTP_METHOD_POST}. If
- * postBody is null, then httpMethod is set to {@link #HTTP_METHOD_GET}.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero. The {@link #httpMethod}
+ * is inferred from {@code postBody}. If {@code postBody} is non-null then {@link #httpMethod} is
+ * set to {@link #HTTP_METHOD_POST}. If {@code postBody} is null then {@link #httpMethod} is set
+ * to {@link #HTTP_METHOD_GET}.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
* @param postBody {@link #httpBody} The body of the HTTP request, which is also used to infer the
* {@link #httpMethod}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public DataSpec(
Uri uri,
@Nullable byte[] postBody,
@@ -249,7 +543,7 @@
@Flags int flags) {
this(
uri,
- /* httpMethod= */ inferHttpMethod(postBody),
+ /* httpMethod= */ postBody != null ? HTTP_METHOD_POST : HTTP_METHOD_GET,
/* httpBody= */ postBody,
absoluteStreamPosition,
position,
@@ -259,17 +553,20 @@
}
/**
- * Construct a data spec where {@link #position} may differ from {@link #absoluteStreamPosition}.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
* @param httpMethod {@link #httpMethod}.
* @param httpBody {@link #httpBody}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public DataSpec(
Uri uri,
@HttpMethod int httpMethod,
@@ -292,18 +589,20 @@
}
/**
- * Construct a data spec with request parameters to be used as HTTP headers inside HTTP requests.
+ * Construct a instance where {@link #uriPositionOffset} may be non-zero.
*
+ * @deprecated Use {@link Builder}.
* @param uri {@link #uri}.
* @param httpMethod {@link #httpMethod}.
* @param httpBody {@link #httpBody}.
- * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
+ * @param absoluteStreamPosition The sum of {@link #uriPositionOffset} and {@link #position}.
* @param position {@link #position}.
* @param length {@link #length}.
* @param key {@link #key}.
* @param flags {@link #flags}.
* @param httpRequestHeaders {@link #httpRequestHeaders}.
*/
+ @Deprecated
public DataSpec(
Uri uri,
@HttpMethod int httpMethod,
@@ -314,18 +613,47 @@
@Nullable String key,
@Flags int flags,
Map<String, String> httpRequestHeaders) {
- Assertions.checkArgument(absoluteStreamPosition >= 0);
+ this(
+ uri,
+ /* uriPositionOffset= */ absoluteStreamPosition - position,
+ httpMethod,
+ httpBody,
+ httpRequestHeaders,
+ position,
+ length,
+ key,
+ flags,
+ /* customData= */ null);
+ }
+
+ @SuppressWarnings("deprecation")
+ private DataSpec(
+ Uri uri,
+ long uriPositionOffset,
+ @HttpMethod int httpMethod,
+ @Nullable byte[] httpBody,
+ Map<String, String> httpRequestHeaders,
+ long position,
+ long length,
+ @Nullable String key,
+ @Flags int flags,
+ @Nullable Object customData) {
+ // TODO: Replace this assertion with a stricter one checking "uriPositionOffset >= 0", after
+ // validating there are no violations in ExoPlayer and 1P apps.
+ Assertions.checkArgument(uriPositionOffset + position >= 0);
Assertions.checkArgument(position >= 0);
Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET);
this.uri = uri;
+ this.uriPositionOffset = uriPositionOffset;
this.httpMethod = httpMethod;
- this.httpBody = (httpBody != null && httpBody.length != 0) ? httpBody : null;
- this.absoluteStreamPosition = absoluteStreamPosition;
+ this.httpBody = httpBody != null && httpBody.length != 0 ? httpBody : null;
+ this.httpRequestHeaders = Collections.unmodifiableMap(new HashMap<>(httpRequestHeaders));
this.position = position;
+ this.absoluteStreamPosition = uriPositionOffset + position;
this.length = length;
this.key = key;
this.flags = flags;
- this.httpRequestHeaders = Collections.unmodifiableMap(new HashMap<>(httpRequestHeaders));
+ this.customData = customData;
}
/**
@@ -337,50 +665,17 @@
return (this.flags & flag) == flag;
}
- @Override
- public String toString() {
- return "DataSpec["
- + getHttpMethodString()
- + " "
- + uri
- + ", "
- + Arrays.toString(httpBody)
- + ", "
- + absoluteStreamPosition
- + ", "
- + position
- + ", "
- + length
- + ", "
- + key
- + ", "
- + flags
- + "]";
- }
-
/**
- * Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the {@link
- * #httpMethod}.
+ * Returns the uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the
+ * {@link #httpMethod}.
*/
public final String getHttpMethodString() {
return getStringForHttpMethod(httpMethod);
}
- /**
- * Returns an uppercase HTTP method name (e.g., "GET", "POST", "HEAD") corresponding to the {@code
- * httpMethod}.
- */
- public static String getStringForHttpMethod(@HttpMethod int httpMethod) {
- switch (httpMethod) {
- case HTTP_METHOD_GET:
- return "GET";
- case HTTP_METHOD_POST:
- return "POST";
- case HTTP_METHOD_HEAD:
- return "HEAD";
- default:
- throw new AssertionError(httpMethod);
- }
+ /** Returns a {@link DataSpec.Builder} initialized with the values of this instance. */
+ public DataSpec.Builder buildUpon() {
+ return new Builder(this);
}
/**
@@ -407,14 +702,15 @@
} else {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition + offset,
+ httpRequestHeaders,
position + offset,
length,
key,
flags,
- httpRequestHeaders);
+ customData);
}
}
@@ -427,62 +723,76 @@
public DataSpec withUri(Uri uri) {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
flags,
- httpRequestHeaders);
+ customData);
}
/**
- * Returns a copy of this data spec with the specified request headers.
+ * Returns a copy of this data spec with the specified HTTP request headers. Headers already in
+ * the data spec are not copied to the new instance.
*
- * @param requestHeaders The HTTP request headers.
- * @return The copied data spec with the specified request headers.
+ * @param httpRequestHeaders The HTTP request headers.
+ * @return The copied data spec with the specified HTTP request headers.
*/
- public DataSpec withRequestHeaders(Map<String, String> requestHeaders) {
+ public DataSpec withRequestHeaders(Map<String, String> httpRequestHeaders) {
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
flags,
- requestHeaders);
+ customData);
}
/**
- * Returns a copy this data spec with additional request headers.
+ * Returns a copy this data spec with additional HTTP request headers. Headers in {@code
+ * additionalHttpRequestHeaders} will overwrite any headers already in the data spec that have the
+ * same keys.
*
- * <p>Note: Values in {@code requestHeaders} will overwrite values with the same header key that
- * were previously set in this instance's {@code #httpRequestHeaders}.
- *
- * @param requestHeaders The additional HTTP request headers.
- * @return The copied data with the additional HTTP request headers.
+ * @param additionalHttpRequestHeaders The additional HTTP request headers.
+ * @return The copied data spec with the additional HTTP request headers.
*/
- public DataSpec withAdditionalHeaders(Map<String, String> requestHeaders) {
- Map<String, String> totalHeaders = new HashMap<>(this.httpRequestHeaders);
- totalHeaders.putAll(requestHeaders);
-
+ public DataSpec withAdditionalHeaders(Map<String, String> additionalHttpRequestHeaders) {
+ Map<String, String> httpRequestHeaders = new HashMap<>(this.httpRequestHeaders);
+ httpRequestHeaders.putAll(additionalHttpRequestHeaders);
return new DataSpec(
uri,
+ uriPositionOffset,
httpMethod,
httpBody,
- absoluteStreamPosition,
+ httpRequestHeaders,
position,
length,
key,
flags,
- totalHeaders);
+ customData);
}
- @HttpMethod
- private static int inferHttpMethod(@Nullable byte[] postBody) {
- return postBody != null ? HTTP_METHOD_POST : HTTP_METHOD_GET;
+ @Override
+ public String toString() {
+ return "DataSpec["
+ + getHttpMethodString()
+ + " "
+ + uri
+ + ", "
+ + position
+ + ", "
+ + length
+ + ", "
+ + key
+ + ", "
+ + flags
+ + "]";
}
}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java
index 4a2959a..3360e88 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java
@@ -18,194 +18,15 @@
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ParserException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-/**
- * Provides static utility methods for manipulating various types of codec specific data.
- */
+/** Provides utilities for handling various types of codec-specific data. */
public final class CodecSpecificDataUtil {
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
- private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
-
- private static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] {
- 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
- };
-
- private static final int AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID = -1;
- /**
- * In the channel configurations below, <A> indicates a single channel element; (A, B) indicates a
- * channel pair element; and [A] indicates a low-frequency effects element.
- * The speaker mapping short forms used are:
- * - FC: front center
- * - BC: back center
- * - FL/FR: front left/right
- * - FCL/FCR: front center left/right
- * - FTL/FTR: front top left/right
- * - SL/SR: back surround left/right
- * - BL/BR: back left/right
- * - LFE: low frequency effects
- */
- private static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE =
- new int[] {
- 0,
- 1, /* mono: <FC> */
- 2, /* stereo: (FL, FR) */
- 3, /* 3.0: <FC>, (FL, FR) */
- 4, /* 4.0: <FC>, (FL, FR), <BC> */
- 5, /* 5.0 back: <FC>, (FL, FR), (SL, SR) */
- 6, /* 5.1 back: <FC>, (FL, FR), (SL, SR), <BC>, [LFE] */
- 8, /* 7.1 wide back: <FC>, (FCL, FCR), (FL, FR), (SL, SR), [LFE] */
- AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
- AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
- AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
- 7, /* 6.1: <FC>, (FL, FR), (SL, SR), <RC>, [LFE] */
- 8, /* 7.1: <FC>, (FL, FR), (SL, SR), (BL, BR), [LFE] */
- AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID,
- 8, /* 7.1 top: <FC>, (FL, FR), (SL, SR), [LFE], (FTL, FTR) */
- AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID
- };
-
- // Advanced Audio Coding Low-Complexity profile.
- private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2;
- // Spectral Band Replication.
- private static final int AUDIO_OBJECT_TYPE_SBR = 5;
- // Error Resilient Bit-Sliced Arithmetic Coding.
- private static final int AUDIO_OBJECT_TYPE_ER_BSAC = 22;
- // Parametric Stereo.
- private static final int AUDIO_OBJECT_TYPE_PS = 29;
- // Escape code for extended audio object types.
- private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31;
-
- private CodecSpecificDataUtil() {}
-
- /**
- * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
- *
- * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse.
- * @return A pair consisting of the sample rate in Hz and the channel count.
- * @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
- */
- public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig)
- throws ParserException {
- return parseAacAudioSpecificConfig(new ParsableBitArray(audioSpecificConfig), false);
- }
-
- /**
- * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
- *
- * @param bitArray A {@link ParsableBitArray} containing the AudioSpecificConfig to parse. The
- * position is advanced to the end of the AudioSpecificConfig.
- * @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
- * knowing the length of the configuration payload.
- * @return A pair consisting of the sample rate in Hz and the channel count.
- * @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
- */
- public static Pair<Integer, Integer> parseAacAudioSpecificConfig(
- ParsableBitArray bitArray, boolean forceReadToEnd) throws ParserException {
- int audioObjectType = getAacAudioObjectType(bitArray);
- int sampleRate = getAacSamplingFrequency(bitArray);
- int channelConfiguration = bitArray.readBits(4);
- if (audioObjectType == AUDIO_OBJECT_TYPE_SBR || audioObjectType == AUDIO_OBJECT_TYPE_PS) {
- // For an AAC bitstream using spectral band replication (SBR) or parametric stereo (PS) with
- // explicit signaling, we return the extension sampling frequency as the sample rate of the
- // content; this is identical to the sample rate of the decoded output but may differ from
- // the sample rate set above.
- // Use the extensionSamplingFrequencyIndex.
- sampleRate = getAacSamplingFrequency(bitArray);
- audioObjectType = getAacAudioObjectType(bitArray);
- if (audioObjectType == AUDIO_OBJECT_TYPE_ER_BSAC) {
- // Use the extensionChannelConfiguration.
- channelConfiguration = bitArray.readBits(4);
- }
- }
-
- if (forceReadToEnd) {
- switch (audioObjectType) {
- case 1:
- case 2:
- case 3:
- case 4:
- case 6:
- case 7:
- case 17:
- case 19:
- case 20:
- case 21:
- case 22:
- case 23:
- parseGaSpecificConfig(bitArray, audioObjectType, channelConfiguration);
- break;
- default:
- throw new ParserException("Unsupported audio object type: " + audioObjectType);
- }
- switch (audioObjectType) {
- case 17:
- case 19:
- case 20:
- case 21:
- case 22:
- case 23:
- int epConfig = bitArray.readBits(2);
- if (epConfig == 2 || epConfig == 3) {
- throw new ParserException("Unsupported epConfig: " + epConfig);
- }
- break;
- }
- }
- // For supported containers, bits_to_decode() is always 0.
- int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration];
- Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID);
- return Pair.create(sampleRate, channelCount);
- }
-
- /**
- * Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
- *
- * @param sampleRate The sample rate in Hz.
- * @param channelCount The channel count.
- * @return The AudioSpecificConfig.
- */
- public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int channelCount) {
- int sampleRateIndex = C.INDEX_UNSET;
- for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) {
- if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) {
- sampleRateIndex = i;
- }
- }
- int channelConfig = C.INDEX_UNSET;
- for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) {
- if (channelCount == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) {
- channelConfig = i;
- }
- }
- if (sampleRate == C.INDEX_UNSET || channelConfig == C.INDEX_UNSET) {
- throw new IllegalArgumentException(
- "Invalid sample rate or number of channels: " + sampleRate + ", " + channelCount);
- }
- return buildAacAudioSpecificConfig(AUDIO_OBJECT_TYPE_AAC_LC, sampleRateIndex, channelConfig);
- }
-
- /**
- * Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
- *
- * @param audioObjectType The audio object type.
- * @param sampleRateIndex The sample rate index.
- * @param channelConfig The channel configuration.
- * @return The AudioSpecificConfig.
- */
- public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sampleRateIndex,
- int channelConfig) {
- byte[] specificConfig = new byte[2];
- specificConfig[0] = (byte) (((audioObjectType << 3) & 0xF8) | ((sampleRateIndex >> 1) & 0x07));
- specificConfig[1] = (byte) (((sampleRateIndex << 7) & 0x80) | ((channelConfig << 3) & 0x78));
- return specificConfig;
- }
-
/**
* Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
@@ -288,7 +109,8 @@
* @return The individual NAL units, or null if the input did not consist of NAL start code
* delimited units.
*/
- public static @Nullable byte[][] splitNalUnits(byte[] data) {
+ @Nullable
+ public static byte[][] splitNalUnits(byte[] data) {
if (!isNalStartCode(data, 0)) {
// data does not consist of NAL start code delimited units.
return null;
@@ -346,64 +168,5 @@
return true;
}
- /**
- * Returns the AAC audio object type as specified in 14496-3 (2005) Table 1.14.
- *
- * @param bitArray The bit array containing the audio specific configuration.
- * @return The audio object type.
- */
- private static int getAacAudioObjectType(ParsableBitArray bitArray) {
- int audioObjectType = bitArray.readBits(5);
- if (audioObjectType == AUDIO_OBJECT_TYPE_ESCAPE) {
- audioObjectType = 32 + bitArray.readBits(6);
- }
- return audioObjectType;
- }
-
- /**
- * Returns the AAC sampling frequency (or extension sampling frequency) as specified in 14496-3
- * (2005) Table 1.13.
- *
- * @param bitArray The bit array containing the audio specific configuration.
- * @return The sampling frequency.
- */
- private static int getAacSamplingFrequency(ParsableBitArray bitArray) {
- int samplingFrequency;
- int frequencyIndex = bitArray.readBits(4);
- if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) {
- samplingFrequency = bitArray.readBits(24);
- } else {
- Assertions.checkArgument(frequencyIndex < 13);
- samplingFrequency = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex];
- }
- return samplingFrequency;
- }
-
- private static void parseGaSpecificConfig(ParsableBitArray bitArray, int audioObjectType,
- int channelConfiguration) {
- bitArray.skipBits(1); // frameLengthFlag.
- boolean dependsOnCoreDecoder = bitArray.readBit();
- if (dependsOnCoreDecoder) {
- bitArray.skipBits(14); // coreCoderDelay.
- }
- boolean extensionFlag = bitArray.readBit();
- if (channelConfiguration == 0) {
- throw new UnsupportedOperationException(); // TODO: Implement programConfigElement();
- }
- if (audioObjectType == 6 || audioObjectType == 20) {
- bitArray.skipBits(3); // layerNr.
- }
- if (extensionFlag) {
- if (audioObjectType == 22) {
- bitArray.skipBits(16); // numOfSubFrame (5), layer_length(11).
- }
- if (audioObjectType == 17 || audioObjectType == 19 || audioObjectType == 20
- || audioObjectType == 23) {
- // aacSectionDataResilienceFlag, aacScalefactorDataResilienceFlag,
- // aacSpectralDataResilienceFlag.
- bitArray.skipBits(3);
- }
- bitArray.skipBits(1); // extensionFlag3.
- }
- }
+ private CodecSpecificDataUtil() {}
}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java
new file mode 100644
index 0000000..e8eb0d0
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/CopyOnWriteMultiset.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.util;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An unordered collection of elements that allows duplicates, but also allows access to a set of
+ * unique elements.
+ *
+ * <p>This class is thread-safe using the same method as {@link
+ * java.util.concurrent.CopyOnWriteArrayList}. Mutation methods cause the underlying data to be
+ * copied. {@link #elementSet()} and {@link #iterator()} return snapshots that are unaffected by
+ * subsequent mutations.
+ *
+ * <p>Iterating directly on this class reveals duplicate elements. Unique elements can be accessed
+ * via {@link #elementSet()}. Iteration order for both of these is not defined.
+ *
+ * @param <E> The type of element being stored.
+ */
+public final class CopyOnWriteMultiset<E> implements Iterable<E> {
+
+ private final Object lock;
+
+ @GuardedBy("lock")
+ private final Map<E, Integer> elementCounts;
+
+ @GuardedBy("lock")
+ private Set<E> elementSet;
+
+ @GuardedBy("lock")
+ private List<E> elements;
+
+ public CopyOnWriteMultiset() {
+ lock = new Object();
+ elementCounts = new HashMap<>();
+ elementSet = Collections.emptySet();
+ elements = Collections.emptyList();
+ }
+
+ /**
+ * Adds {@code element} to the multiset.
+ *
+ * @param element The element to be added.
+ */
+ public void add(E element) {
+ synchronized (lock) {
+ List<E> elements = new ArrayList<>(this.elements);
+ elements.add(element);
+ this.elements = Collections.unmodifiableList(elements);
+
+ @Nullable Integer count = elementCounts.get(element);
+ if (count == null) {
+ Set<E> elementSet = new HashSet<>(this.elementSet);
+ elementSet.add(element);
+ this.elementSet = Collections.unmodifiableSet(elementSet);
+ }
+ elementCounts.put(element, count != null ? count + 1 : 1);
+ }
+ }
+
+ /**
+ * Removes {@code element} from the multiset.
+ *
+ * @param element The element to be removed.
+ */
+ public void remove(E element) {
+ synchronized (lock) {
+ @Nullable Integer count = elementCounts.get(element);
+ if (count == null) {
+ return;
+ }
+
+ List<E> elements = new ArrayList<>(this.elements);
+ elements.remove(element);
+ this.elements = Collections.unmodifiableList(elements);
+
+ if (count == 1) {
+ elementCounts.remove(element);
+ Set<E> elementSet = new HashSet<>(this.elementSet);
+ elementSet.remove(element);
+ this.elementSet = Collections.unmodifiableSet(elementSet);
+ } else {
+ elementCounts.put(element, count - 1);
+ }
+ }
+ }
+
+ /**
+ * Returns a snapshot of the unique elements currently in this multiset.
+ *
+ * <p>Changes to the underlying multiset are not reflected in the returned value.
+ *
+ * @return An unmodifiable set containing the unique elements in this multiset.
+ */
+ public Set<E> elementSet() {
+ synchronized (lock) {
+ return elementSet;
+ }
+ }
+
+ /**
+ * Returns an iterator over a snapshot of all the elements currently in this multiset (including
+ * duplicates).
+ *
+ * <p>Changes to the underlying multiset are not reflected in the returned value.
+ *
+ * @return An unmodifiable iterator over all the elements in this multiset (including duplicates).
+ */
+ @Override
+ public Iterator<E> iterator() {
+ synchronized (lock) {
+ return elements.iterator();
+ }
+ }
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Function.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Function.java
new file mode 100644
index 0000000..900f32d
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Function.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.util;
+
+/**
+ * A functional interface representing a function taking one argument and returning a result.
+ *
+ * @param <T> The input type of the function.
+ * @param <R> The output type of the function.
+ */
+public interface Function<T, R> {
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param t The function argument.
+ * @return The function result, which may be {@code null}.
+ */
+ R apply(T t);
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java
index a29460b..e5e6f88 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java
@@ -21,6 +21,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.net.UnknownHostException;
/** Wrapper around {@link android.util.Log} which allows to set the log level. */
public final class Log {
@@ -69,7 +70,8 @@
}
/**
- * Sets whether stack traces of {@link Throwable}s will be logged to logcat.
+ * Sets whether stack traces of {@link Throwable}s will be logged to logcat. Stack trace logging
+ * is enabled by default.
*
* @param logStackTraces Whether stack traces will be logged.
*/
@@ -86,11 +88,7 @@
/** @see android.util.Log#d(String, String, Throwable) */
public static void d(String tag, String message, @Nullable Throwable throwable) {
- if (!logStackTraces) {
- d(tag, appendThrowableMessage(message, throwable));
- } else if (logLevel == LOG_LEVEL_ALL) {
- android.util.Log.d(tag, message, throwable);
- }
+ d(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#i(String, String) */
@@ -102,11 +100,7 @@
/** @see android.util.Log#i(String, String, Throwable) */
public static void i(String tag, String message, @Nullable Throwable throwable) {
- if (!logStackTraces) {
- i(tag, appendThrowableMessage(message, throwable));
- } else if (logLevel <= LOG_LEVEL_INFO) {
- android.util.Log.i(tag, message, throwable);
- }
+ i(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#w(String, String) */
@@ -118,11 +112,7 @@
/** @see android.util.Log#w(String, String, Throwable) */
public static void w(String tag, String message, @Nullable Throwable throwable) {
- if (!logStackTraces) {
- w(tag, appendThrowableMessage(message, throwable));
- } else if (logLevel <= LOG_LEVEL_WARNING) {
- android.util.Log.w(tag, message, throwable);
- }
+ w(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#e(String, String) */
@@ -134,18 +124,54 @@
/** @see android.util.Log#e(String, String, Throwable) */
public static void e(String tag, String message, @Nullable Throwable throwable) {
- if (!logStackTraces) {
- e(tag, appendThrowableMessage(message, throwable));
- } else if (logLevel <= LOG_LEVEL_ERROR) {
- android.util.Log.e(tag, message, throwable);
+ e(tag, appendThrowableString(message, throwable));
+ }
+
+ /**
+ * Returns a string representation of a {@link Throwable} suitable for logging, taking into
+ * account whether {@link #setLogStackTraces(boolean)} stack trace logging} is enabled.
+ *
+ * <p>Stack trace logging may be unconditionally suppressed for some expected failure modes (e.g.,
+ * {@link Throwable Throwables} that are expected if the device doesn't have network connectivity)
+ * to avoid log spam.
+ *
+ * @param throwable The {@link Throwable}.
+ * @return The string representation of the {@link Throwable}.
+ */
+ @Nullable
+ public static String getThrowableString(@Nullable Throwable throwable) {
+ if (throwable == null) {
+ return null;
+ } else if (isCausedByUnknownHostException(throwable)) {
+ // UnknownHostException implies the device doesn't have network connectivity.
+ // UnknownHostException.getMessage() may return a string that's more verbose than desired for
+ // logging an expected failure mode. Conversely, android.util.Log.getStackTraceString has
+ // special handling to return the empty string, which can result in logging that doesn't
+ // indicate the failure mode at all. Hence we special case this exception to always return a
+ // concise but useful message.
+ return "UnknownHostException (no network)";
+ } else if (!logStackTraces) {
+ return throwable.getMessage();
+ } else {
+ return android.util.Log.getStackTraceString(throwable).trim().replace("\t", " ");
}
}
- private static String appendThrowableMessage(String message, @Nullable Throwable throwable) {
- if (throwable == null) {
- return message;
+ private static String appendThrowableString(String message, @Nullable Throwable throwable) {
+ @Nullable String throwableString = getThrowableString(throwable);
+ if (!TextUtils.isEmpty(throwableString)) {
+ message += "\n " + throwableString.replace("\n", "\n ") + '\n';
}
- String throwableMessage = throwable.getMessage();
- return TextUtils.isEmpty(throwableMessage) ? message : message + " - " + throwableMessage;
+ return message;
+ }
+
+ private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) {
+ while (throwable != null) {
+ if (throwable instanceof UnknownHostException) {
+ return true;
+ }
+ throwable = throwable.getCause();
+ }
+ return false;
}
}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
index e61ab83..e9e74d4 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
@@ -97,6 +97,7 @@
public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs";
public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
+ public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait";
private static final ArrayList<CustomMimeType> customMimeTypes = new ArrayList<>();
@@ -122,24 +123,33 @@
customMimeTypes.add(customMimeType);
}
- /** Returns whether the given string is an audio mime type. */
+ /** Returns whether the given string is an audio MIME type. */
public static boolean isAudio(@Nullable String mimeType) {
return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType));
}
- /** Returns whether the given string is a video mime type. */
+ /** Returns whether the given string is a video MIME type. */
public static boolean isVideo(@Nullable String mimeType) {
return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType));
}
- /** Returns whether the given string is a text mime type. */
+ /**
+ * Returns whether the given string is a text MIME type, including known text types that use
+ * "application" as their base type.
+ */
public static boolean isText(@Nullable String mimeType) {
- return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType));
- }
-
- /** Returns whether the given string is an application mime type. */
- public static boolean isApplication(@Nullable String mimeType) {
- return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType));
+ return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType))
+ || APPLICATION_CEA608.equals(mimeType)
+ || APPLICATION_CEA708.equals(mimeType)
+ || APPLICATION_MP4CEA608.equals(mimeType)
+ || APPLICATION_SUBRIP.equals(mimeType)
+ || APPLICATION_TTML.equals(mimeType)
+ || APPLICATION_TX3G.equals(mimeType)
+ || APPLICATION_MP4VTT.equals(mimeType)
+ || APPLICATION_RAWCC.equals(mimeType)
+ || APPLICATION_VOBSUB.equals(mimeType)
+ || APPLICATION_PGS.equals(mimeType)
+ || APPLICATION_DVBSUBS.equals(mimeType);
}
/**
@@ -173,13 +183,14 @@
* @param codecs The codecs attribute.
* @return The derived video mimeType, or null if it could not be derived.
*/
- public static @Nullable String getVideoMediaMimeType(@Nullable String codecs) {
+ @Nullable
+ public static String getVideoMediaMimeType(@Nullable String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = Util.splitCodecs(codecs);
for (String codec : codecList) {
- String mimeType = getMediaMimeType(codec);
+ @Nullable String mimeType = getMediaMimeType(codec);
if (mimeType != null && isVideo(mimeType)) {
return mimeType;
}
@@ -193,13 +204,14 @@
* @param codecs The codecs attribute.
* @return The derived audio mimeType, or null if it could not be derived.
*/
- public static @Nullable String getAudioMediaMimeType(@Nullable String codecs) {
+ @Nullable
+ public static String getAudioMediaMimeType(@Nullable String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = Util.splitCodecs(codecs);
for (String codec : codecList) {
- String mimeType = getMediaMimeType(codec);
+ @Nullable String mimeType = getMediaMimeType(codec);
if (mimeType != null && isAudio(mimeType)) {
return mimeType;
}
@@ -208,12 +220,34 @@
}
/**
+ * Derives a text sample mimeType from a codecs attribute.
+ *
+ * @param codecs The codecs attribute.
+ * @return The derived text mimeType, or null if it could not be derived.
+ */
+ @Nullable
+ public static String getTextMediaMimeType(@Nullable String codecs) {
+ if (codecs == null) {
+ return null;
+ }
+ String[] codecList = Util.splitCodecs(codecs);
+ for (String codec : codecList) {
+ @Nullable String mimeType = getMediaMimeType(codec);
+ if (mimeType != null && isText(mimeType)) {
+ return mimeType;
+ }
+ }
+ return null;
+ }
+
+ /**
* Derives a mimeType from a codec identifier, as defined in RFC 6381.
*
* @param codec The codec identifier to derive.
* @return The mimeType, or null if it could not be derived.
*/
- public static @Nullable String getMediaMimeType(@Nullable String codec) {
+ @Nullable
+ public static String getMediaMimeType(@Nullable String codec) {
if (codec == null) {
return null;
}
@@ -234,7 +268,7 @@
} else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
return MimeTypes.VIDEO_VP8;
} else if (codec.startsWith("mp4a")) {
- String mimeType = null;
+ @Nullable String mimeType = null;
if (codec.startsWith("mp4a.")) {
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
if (objectTypeString.length() >= 2) {
@@ -243,7 +277,7 @@
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
} catch (NumberFormatException ignored) {
- // ignored
+ // Ignored.
}
}
}
@@ -266,6 +300,14 @@
return MimeTypes.AUDIO_VORBIS;
} else if (codec.startsWith("flac")) {
return MimeTypes.AUDIO_FLAC;
+ } else if (codec.startsWith("stpp")) {
+ return MimeTypes.APPLICATION_TTML;
+ } else if (codec.startsWith("wvtt")) {
+ return MimeTypes.TEXT_VTT;
+ } else if (codec.contains("cea708")) {
+ return MimeTypes.APPLICATION_CEA708;
+ } else if (codec.contains("eia608") || codec.contains("cea608")) {
+ return MimeTypes.APPLICATION_CEA608;
} else {
return getCustomMimeTypeForCodec(codec);
}
@@ -342,12 +384,7 @@
return C.TRACK_TYPE_AUDIO;
} else if (isVideo(mimeType)) {
return C.TRACK_TYPE_VIDEO;
- } else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType)
- || APPLICATION_CEA708.equals(mimeType) || APPLICATION_MP4CEA608.equals(mimeType)
- || APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType)
- || APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType)
- || APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType)
- || APPLICATION_PGS.equals(mimeType) || APPLICATION_DVBSUBS.equals(mimeType)) {
+ } else if (isText(mimeType)) {
return C.TRACK_TYPE_TEXT;
} else if (APPLICATION_ID3.equals(mimeType)
|| APPLICATION_EMSG.equals(mimeType)
@@ -405,7 +442,8 @@
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
* contain a forward slash character ({@code '/'}).
*/
- private static @Nullable String getTopLevelType(@Nullable String mimeType) {
+ @Nullable
+ private static String getTopLevelType(@Nullable String mimeType) {
if (mimeType == null) {
return null;
}
@@ -416,7 +454,8 @@
return mimeType.substring(0, indexOfSlash);
}
- private static @Nullable String getCustomMimeTypeForCodec(String codec) {
+ @Nullable
+ private static String getCustomMimeTypeForCodec(String codec) {
int customMimeTypeCount = customMimeTypes.size();
for (int i = 0; i < customMimeTypeCount; i++) {
CustomMimeType customMimeType = customMimeTypes.get(i);
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java
index fc1bc65..963e43f 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java
@@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.util;
+import com.google.android.exoplayer2.C;
+import java.nio.charset.Charset;
+
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
@@ -278,6 +281,31 @@
}
/**
+ * Reads the next {@code length} bytes as a UTF-8 string. Must only be called when the position is
+ * byte aligned.
+ *
+ * @param length The number of bytes to read.
+ * @return The string encoded by the bytes in UTF-8.
+ */
+ public String readBytesAsString(int length) {
+ return readBytesAsString(length, Charset.forName(C.UTF8_NAME));
+ }
+
+ /**
+ * Reads the next {@code length} bytes as a string encoded in {@link Charset}. Must only be called
+ * when the position is byte aligned.
+ *
+ * @param length The number of bytes to read.
+ * @param charset The character set of the encoded characters.
+ * @return The string encoded by the bytes in the specified character set.
+ */
+ public String readBytesAsString(int length, Charset charset) {
+ byte[] bytes = new byte[length];
+ readBytes(bytes, 0, length);
+ return new String(bytes, charset);
+ }
+
+ /**
* Overwrites {@code numBits} from this array using the {@code numBits} least significant bits
* from {@code value}. Bits are written in order from most significant to least significant. The
* read position is advanced by {@code numBits}.
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Supplier.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Supplier.java
new file mode 100644
index 0000000..723047b
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Supplier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.util;
+
+/**
+ * A functional interface representing a supplier of results.
+ *
+ * @param <T> The type of results supplied by this supplier.
+ */
+public interface Supplier<T> {
+
+ /** Gets a result. */
+ T get();
+}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java
index 8fb409c..823fd1a 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java
@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer2.util;
-import android.annotation.TargetApi;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
/**
@@ -49,12 +49,12 @@
}
}
- @TargetApi(18)
+ @RequiresApi(18)
private static void beginSectionV18(String sectionName) {
android.os.Trace.beginSection(sectionName);
}
- @TargetApi(18)
+ @RequiresApi(18)
private static void endSectionV18() {
android.os.Trace.endSection();
}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java
new file mode 100644
index 0000000..8ca2e8f
--- /dev/null
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import javax.annotation.Nonnull;
+import javax.annotation.meta.TypeQualifierDefault;
+import javax.annotation.meta.When;
+
+/**
+ * Annotation for specifiying unknown nullness. Useful for clearing the effects of an automatically
+ * propagated {@link Nonnull} annotation.
+ */
+@Nonnull(when = When.UNKNOWN)
+@TypeQualifierDefault(ElementType.TYPE_USE)
+@Retention(RetentionPolicy.CLASS)
+public @interface UnknownNull {}
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
index c3b1373..60fe1a3 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
@@ -19,7 +19,6 @@
import android.Manifest.permission;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.app.Activity;
import android.app.UiModeManager;
import android.content.ComponentName;
@@ -47,9 +46,11 @@
import android.view.SurfaceView;
import android.view.WindowManager;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.upstream.DataSource;
import java.io.ByteArrayOutputStream;
@@ -60,6 +61,7 @@
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.Charset;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
@@ -178,44 +180,73 @@
* @param uris {@link Uri}s that may require {@link permission#READ_EXTERNAL_STORAGE} to read.
* @return Whether a permission request was made.
*/
- @TargetApi(23)
public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) {
if (Util.SDK_INT < 23) {
return false;
}
for (Uri uri : uris) {
if (isLocalFileUri(uri)) {
- if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
- activity.requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0);
- return true;
- }
- break;
+ return requestExternalStoragePermission(activity);
}
}
return false;
}
/**
- * Returns whether it may be possible to load the given URIs based on the network security
- * policy's cleartext traffic permissions.
+ * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE}
+ * permission for the specified {@link MediaItem media items}, requesting the permission if
+ * necessary.
*
- * @param uris A list of URIs that will be loaded.
- * @return Whether it may be possible to load the given URIs.
+ * @param activity The host activity for checking and requesting the permission.
+ * @param mediaItems {@link MediaItem Media items}s that may require {@link
+ * permission#READ_EXTERNAL_STORAGE} to read.
+ * @return Whether a permission request was made.
*/
- @TargetApi(24)
- public static boolean checkCleartextTrafficPermitted(Uri... uris) {
+ public static boolean maybeRequestReadExternalStoragePermission(
+ Activity activity, MediaItem... mediaItems) {
+ if (Util.SDK_INT < 23) {
+ return false;
+ }
+ for (MediaItem mediaItem : mediaItems) {
+ if (mediaItem.playbackProperties == null) {
+ continue;
+ }
+ if (isLocalFileUri(mediaItem.playbackProperties.sourceUri)) {
+ return requestExternalStoragePermission(activity);
+ }
+ for (int i = 0; i < mediaItem.playbackProperties.subtitles.size(); i++) {
+ if (isLocalFileUri(mediaItem.playbackProperties.subtitles.get(i).uri)) {
+ return requestExternalStoragePermission(activity);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether it may be possible to load the URIs of the given media items based on the
+ * network security policy's cleartext traffic permissions.
+ *
+ * @param mediaItems A list of {@link MediaItem media items}.
+ * @return Whether it may be possible to load the URIs of the given media items.
+ */
+ public static boolean checkCleartextTrafficPermitted(MediaItem... mediaItems) {
if (Util.SDK_INT < 24) {
// We assume cleartext traffic is permitted.
return true;
}
- for (Uri uri : uris) {
- if ("http".equals(uri.getScheme())
- && !NetworkSecurityPolicy.getInstance()
- .isCleartextTrafficPermitted(Assertions.checkNotNull(uri.getHost()))) {
- // The security policy prevents cleartext traffic.
+ for (MediaItem mediaItem : mediaItems) {
+ if (mediaItem.playbackProperties == null) {
+ continue;
+ }
+ if (isTrafficRestricted(mediaItem.playbackProperties.sourceUri)) {
return false;
}
+ for (int i = 0; i < mediaItem.playbackProperties.subtitles.size(); i++) {
+ if (isTrafficRestricted(mediaItem.playbackProperties.subtitles.get(i).uri)) {
+ return false;
+ }
+ }
}
return true;
}
@@ -1023,13 +1054,16 @@
}
/**
- * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since
- * the epoch.
+ * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since the
+ * epoch.
*
* @param value The attribute value to decode.
* @return The parsed timestamp in milliseconds since the epoch.
* @throws ParserException if an error occurs parsing the dateTime attribute value.
*/
+ // incompatible types in argument.
+ // dereference of possibly-null reference matcher.group(9)
+ @SuppressWarnings({"nullness:argument.type.incompatible", "nullness:dereference.of.nullable"})
public static long parseXsDateTime(String value) throws ParserException {
Matcher matcher = XS_DATE_TIME_PATTERN.matcher(value);
if (!matcher.matches()) {
@@ -1199,6 +1233,23 @@
}
/**
+ * Converts an array of primitive ints to a list of integers.
+ *
+ * @param ints The ints.
+ * @return The input array in list form.
+ */
+ public static List<Integer> toList(int... ints) {
+ if (ints == null) {
+ return new ArrayList<>();
+ }
+ List<Integer> integers = new ArrayList<>();
+ for (int anInt : ints) {
+ integers.add(anInt);
+ }
+ return integers;
+ }
+
+ /**
* Returns the integer equal to the big-endian concatenation of the characters in {@code string}
* as bytes. The string must be no more than four characters long.
*
@@ -1255,6 +1306,22 @@
}
/**
+ * Returns a string containing a lower-case hex representation of the bytes provided.
+ *
+ * @param bytes The byte data to convert to hex.
+ * @return A String containing the hex representation of {@code bytes}.
+ */
+ public static String toHexString(byte[] bytes) {
+ StringBuilder result = new StringBuilder(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ result
+ .append(Character.forDigit((bytes[i] >> 4) & 0xF, 16))
+ .append(Character.forDigit(bytes[i] & 0xF, 16));
+ }
+ return result.toString();
+ }
+
+ /**
* Returns a string with comma delimited simple names of each object's class.
*
* @param objects The objects whose simple class names should be comma delimited and returned.
@@ -1371,13 +1438,15 @@
}
/**
- * Returns whether {@code encoding} is high resolution (> 16-bit) integer PCM.
+ * Returns whether {@code encoding} is high resolution (> 16-bit) PCM.
*
* @param encoding The encoding of the audio data.
- * @return Whether the encoding is high resolution integer PCM.
+ * @return Whether the encoding is high resolution PCM.
*/
- public static boolean isEncodingHighResolutionIntegerPcm(@C.PcmEncoding int encoding) {
- return encoding == C.ENCODING_PCM_24BIT || encoding == C.ENCODING_PCM_32BIT;
+ public static boolean isEncodingHighResolutionPcm(@C.PcmEncoding int encoding) {
+ return encoding == C.ENCODING_PCM_24BIT
+ || encoding == C.ENCODING_PCM_32BIT
+ || encoding == C.ENCODING_PCM_FLOAT;
}
/**
@@ -1703,7 +1772,8 @@
Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName);
int startOfNotEscaped = 0;
while (percentCharacterCount > 0 && matcher.find()) {
- char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16);
+ char unescapedCharacter =
+ (char) Integer.parseInt(Assertions.checkNotNull(matcher.group(1)), 16);
builder.append(fileName, startOfNotEscaped, matcher.start()).append(unescapedCharacter);
startOfNotEscaped = matcher.end();
percentCharacterCount--;
@@ -2058,14 +2128,14 @@
}
}
- @TargetApi(23)
+ @RequiresApi(23)
private static void getDisplaySizeV23(Display display, Point outSize) {
Display.Mode mode = display.getMode();
outSize.x = mode.getPhysicalWidth();
outSize.y = mode.getPhysicalHeight();
}
- @TargetApi(17)
+ @RequiresApi(17)
private static void getDisplaySizeV17(Display display, Point outSize) {
display.getRealSize(outSize);
}
@@ -2081,12 +2151,12 @@
: new String[] {getLocaleLanguageTag(config.locale)};
}
- @TargetApi(24)
+ @RequiresApi(24)
private static String[] getSystemLocalesV24(Configuration config) {
return Util.split(config.getLocales().toLanguageTags(), ",");
}
- @TargetApi(21)
+ @RequiresApi(21)
private static String getLocaleLanguageTagV21(Locale locale) {
return locale.toLanguageTag();
}
@@ -2147,6 +2217,24 @@
return replacedLanguages;
}
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ private static boolean requestExternalStoragePermission(Activity activity) {
+ if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(
+ new String[] {permission.READ_EXTERNAL_STORAGE}, /* requestCode= */ 0);
+ return true;
+ }
+ return false;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ private static boolean isTrafficRestricted(Uri uri) {
+ return "http".equals(uri.getScheme())
+ && !NetworkSecurityPolicy.getInstance()
+ .isCleartextTrafficPermitted(Assertions.checkNotNull(uri.getHost()));
+ }
+
private static String maybeReplaceGrandfatheredLanguageTags(String languageTag) {
for (int i = 0; i < isoGrandfatheredTagReplacements.length; i += 2) {
if (languageTag.startsWith(isoGrandfatheredTagReplacements[i])) {
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java
index ed2ca9c..d45d6c5 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java
@@ -43,12 +43,11 @@
public final int colorRange;
/**
- * The color transfer characteristicks of the video. Valid values are {@link
- * C#COLOR_TRANSFER_HLG}, {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link
- * Format#NO_VALUE} if unknown.
+ * The color transfer characteristics of the video. Valid values are {@link C#COLOR_TRANSFER_HLG},
+ * {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link Format#NO_VALUE} if
+ * unknown.
*/
- @C.ColorTransfer
- public final int colorTransfer;
+ @C.ColorTransfer public final int colorTransfer;
/** HdrStaticInfo as defined in CTA-861.3, or null if none specified. */
@Nullable public final byte[] hdrStaticInfo;
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/CTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/CTest.java
index 26a7102..ac5edc6 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/CTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/CTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.annotation.SuppressLint;
+import android.media.AudioFormat;
import android.media.MediaCodec;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
@@ -29,11 +30,19 @@
@SuppressLint("InlinedApi")
@Test
- public void testConstants() {
+ public void bufferFlagConstants_equalToMediaCodecConstants() {
// Sanity check that constant values match those defined by the platform.
assertThat(C.BUFFER_FLAG_KEY_FRAME).isEqualTo(MediaCodec.BUFFER_FLAG_KEY_FRAME);
assertThat(C.BUFFER_FLAG_END_OF_STREAM).isEqualTo(MediaCodec.BUFFER_FLAG_END_OF_STREAM);
assertThat(C.CRYPTO_MODE_AES_CTR).isEqualTo(MediaCodec.CRYPTO_MODE_AES_CTR);
}
+ @SuppressLint("InlinedApi")
+ @Test
+ public void encodingConstants_equalToAudioFormatConstants() {
+ // Sanity check that encoding constant values match those defined by the platform.
+ assertThat(C.ENCODING_PCM_16BIT).isEqualTo(AudioFormat.ENCODING_PCM_16BIT);
+ assertThat(C.ENCODING_MP3).isEqualTo(AudioFormat.ENCODING_MP3);
+ assertThat(C.ENCODING_PCM_FLOAT).isEqualTo(AudioFormat.ENCODING_PCM_FLOAT);
+ }
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
index fe2a8c7..135aace 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
@@ -23,13 +23,13 @@
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.drm.DrmInitData;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.video.ColorInfo;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,70 +38,88 @@
@RunWith(AndroidJUnit4.class)
public final class FormatTest {
- private static final List<byte[]> initData;
- static {
- byte[] initData1 = new byte[] {1, 2, 3};
- byte[] initData2 = new byte[] {4, 5, 6};
- List<byte[]> initDataList = new ArrayList<>();
- initDataList.add(initData1);
- initDataList.add(initData2);
- initData = Collections.unmodifiableList(initDataList);
+ @Test
+ public void buildUponFormat_createsEqualFormat() {
+ Format testFormat = createTestFormat();
+ assertThat(testFormat.buildUpon().build()).isEqualTo(testFormat);
}
@Test
- public void testParcelable() {
- DrmInitData.SchemeData drmData1 = new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_MP4,
- TestUtil.buildTestData(128, 1 /* data seed */));
- DrmInitData.SchemeData drmData2 = new DrmInitData.SchemeData(C.UUID_NIL, VIDEO_WEBM,
- TestUtil.buildTestData(128, 1 /* data seed */));
- DrmInitData drmInitData = new DrmInitData(drmData1, drmData2);
- byte[] projectionData = new byte[] {1, 2, 3};
- Metadata metadata = new Metadata(
- new TextInformationFrame("id1", "description1", "value1"),
- new TextInformationFrame("id2", "description2", "value2"));
- ColorInfo colorInfo = new ColorInfo(C.COLOR_SPACE_BT709,
- C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_SDR, new byte[] {1, 2, 3, 4, 5, 6, 7});
-
- Format formatToParcel =
- new Format(
- "id",
- "label",
- C.SELECTION_FLAG_DEFAULT,
- C.ROLE_FLAG_MAIN,
- /* bitrate= */ 1024,
- "codec",
- metadata,
- /* containerMimeType= */ MimeTypes.VIDEO_MP4,
- /* sampleMimeType= */ MimeTypes.VIDEO_H264,
- /* maxInputSize= */ 2048,
- initData,
- drmInitData,
- Format.OFFSET_SAMPLE_RELATIVE,
- /* width= */ 1920,
- /* height= */ 1080,
- /* frameRate= */ 24,
- /* rotationDegrees= */ 90,
- /* pixelWidthHeightRatio= */ 2,
- projectionData,
- C.STEREO_MODE_TOP_BOTTOM,
- colorInfo,
- /* channelCount= */ 6,
- /* sampleRate= */ 44100,
- C.ENCODING_PCM_24BIT,
- /* encoderDelay= */ 1001,
- /* encoderPadding= */ 1002,
- "language",
- /* accessibilityChannel= */ Format.NO_VALUE,
- /* exoMediaCryptoType= */ null);
+ public void parcelFormat_createsEqualFormat_exceptExoMediaCryptoType() {
+ Format formatToParcel = createTestFormat();
Parcel parcel = Parcel.obtain();
formatToParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
Format formatFromParcel = Format.CREATOR.createFromParcel(parcel);
- assertThat(formatFromParcel).isEqualTo(formatToParcel);
+ Format expectedFormat = formatToParcel.buildUpon().setExoMediaCryptoType(null).build();
+
+ assertThat(formatFromParcel.exoMediaCryptoType).isNull();
+ assertThat(formatFromParcel).isEqualTo(expectedFormat);
parcel.recycle();
}
+ private static Format createTestFormat() {
+ byte[] initData1 = new byte[] {1, 2, 3};
+ byte[] initData2 = new byte[] {4, 5, 6};
+ List<byte[]> initializationData = new ArrayList<>();
+ initializationData.add(initData1);
+ initializationData.add(initData2);
+
+ DrmInitData.SchemeData drmData1 =
+ new DrmInitData.SchemeData(
+ WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */));
+ DrmInitData.SchemeData drmData2 =
+ new DrmInitData.SchemeData(
+ C.UUID_NIL, VIDEO_WEBM, TestUtil.buildTestData(128, 1 /* data seed */));
+ DrmInitData drmInitData = new DrmInitData(drmData1, drmData2);
+
+ byte[] projectionData = new byte[] {1, 2, 3};
+
+ Metadata metadata =
+ new Metadata(
+ new TextInformationFrame("id1", "description1", "value1"),
+ new TextInformationFrame("id2", "description2", "value2"));
+
+ ColorInfo colorInfo =
+ new ColorInfo(
+ C.COLOR_SPACE_BT709,
+ C.COLOR_RANGE_LIMITED,
+ C.COLOR_TRANSFER_SDR,
+ new byte[] {1, 2, 3, 4, 5, 6, 7});
+
+ return new Format(
+ "id",
+ "label",
+ "language",
+ C.SELECTION_FLAG_DEFAULT,
+ C.ROLE_FLAG_MAIN,
+ /* averageBitrate= */ 1024,
+ /* peakBitrate= */ 2048,
+ "codec",
+ metadata,
+ /* containerMimeType= */ MimeTypes.VIDEO_MP4,
+ /* sampleMimeType= */ MimeTypes.VIDEO_H264,
+ /* maxInputSize= */ 5000,
+ initializationData,
+ drmInitData,
+ Format.OFFSET_SAMPLE_RELATIVE,
+ /* width= */ 1920,
+ /* height= */ 1080,
+ /* frameRate= */ 24,
+ /* rotationDegrees= */ 90,
+ /* pixelWidthHeightRatio= */ 4,
+ projectionData,
+ C.STEREO_MODE_TOP_BOTTOM,
+ colorInfo,
+ /* channelCount= */ 6,
+ /* sampleRate= */ 44100,
+ C.ENCODING_PCM_24BIT,
+ /* encoderDelay= */ 1001,
+ /* encoderPadding= */ 1002,
+ /* accessibilityChannel= */ 2,
+ /* exoMediaCryptoType= */ ExoMediaCrypto.class);
+ }
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
new file mode 100644
index 0000000..bb47011
--- /dev/null
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.net.Uri;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.offline.StreamKey;
+import com.google.android.exoplayer2.util.MimeTypes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link MediaItem MediaItems}. */
+@RunWith(AndroidJUnit4.class)
+public class MediaItemTest {
+
+ private static final String URI_STRING = "http://www.google.com";
+
+ @Test
+ public void builder_needsSourceUriOrMediaId() {
+ assertThrows(NullPointerException.class, () -> new MediaItem.Builder().build());
+ }
+
+ @Test
+ public void builderWithUri_setsSourceUri() {
+ Uri uri = Uri.parse(URI_STRING);
+
+ MediaItem mediaItem = MediaItem.fromUri(uri);
+
+ assertThat(mediaItem.playbackProperties.sourceUri.toString()).isEqualTo(URI_STRING);
+ assertThat(mediaItem.mediaId).isEqualTo(URI_STRING);
+ assertThat(mediaItem.mediaMetadata).isNotNull();
+ }
+
+ @Test
+ public void builderWithUriAsString_setsSourceUri() {
+ MediaItem mediaItem = MediaItem.fromUri(URI_STRING);
+
+ assertThat(mediaItem.playbackProperties.sourceUri.toString()).isEqualTo(URI_STRING);
+ assertThat(mediaItem.mediaId).isEqualTo(URI_STRING);
+ }
+
+ @Test
+ public void builderSetMimeType_isNullByDefault() {
+ MediaItem mediaItem = MediaItem.fromUri(URI_STRING);
+
+ assertThat(mediaItem.playbackProperties.mimeType).isNull();
+ }
+
+ @Test
+ public void builderSetMimeType_setsMimeType() {
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .build();
+
+ assertThat(mediaItem.playbackProperties.mimeType).isEqualTo(MimeTypes.APPLICATION_MPD);
+ }
+
+ @Test
+ public void builderSetDrmConfig_isNullByDefault() {
+ // Null value by default.
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
+ assertThat(mediaItem.playbackProperties.drmConfiguration).isNull();
+ }
+
+ @Test
+ public void builderSetDrmConfig_setsAllProperties() {
+ Uri licenseUri = Uri.parse(URI_STRING);
+ Map<String, String> requestHeaders = new HashMap<>();
+ requestHeaders.put("Referer", "http://www.google.com");
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri(licenseUri)
+ .setDrmLicenseRequestHeaders(requestHeaders)
+ .setDrmMultiSession(/* multiSession= */ true)
+ .setDrmPlayClearContentWithoutKey(true)
+ .setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
+ .build();
+
+ assertThat(mediaItem.playbackProperties.drmConfiguration).isNotNull();
+ assertThat(mediaItem.playbackProperties.drmConfiguration.uuid).isEqualTo(C.WIDEVINE_UUID);
+ assertThat(mediaItem.playbackProperties.drmConfiguration.licenseUri).isEqualTo(licenseUri);
+ assertThat(mediaItem.playbackProperties.drmConfiguration.requestHeaders)
+ .isEqualTo(requestHeaders);
+ assertThat(mediaItem.playbackProperties.drmConfiguration.multiSession).isTrue();
+ assertThat(mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey).isTrue();
+ assertThat(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes)
+ .containsExactly(C.TRACK_TYPE_AUDIO);
+ }
+
+ @Test
+ public void builderSetDrmSessionForClearPeriods_setsAudioAndVideoTracks() {
+ Uri licenseUri = Uri.parse(URI_STRING);
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri(licenseUri)
+ .setDrmSessionForClearTypes(Arrays.asList(C.TRACK_TYPE_AUDIO))
+ .setDrmSessionForClearPeriods(true)
+ .build();
+
+ assertThat(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes)
+ .containsExactly(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO);
+ }
+
+ @Test
+ public void builderSetDrmUuid_notCalled_throwsIllegalStateException() {
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ // missing uuid
+ .setDrmLicenseUri(Uri.parse(URI_STRING))
+ .build());
+ }
+
+ @Test
+ public void builderSetStreamKeys_setsStreamKeys() {
+ List<StreamKey> streamKeys = new ArrayList<>();
+ streamKeys.add(new StreamKey(1, 0, 0));
+ streamKeys.add(new StreamKey(0, 1, 1));
+
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setStreamKeys(streamKeys).build();
+
+ assertThat(mediaItem.playbackProperties.streamKeys).isEqualTo(streamKeys);
+ }
+
+ @Test
+ public void builderSetSubtitles_setsSubtitles() {
+ List<MediaItem.Subtitle> subtitles =
+ Arrays.asList(
+ new MediaItem.Subtitle(
+ Uri.parse(URI_STRING + "/en"), MimeTypes.APPLICATION_TTML, /* language= */ "en"),
+ new MediaItem.Subtitle(
+ Uri.parse(URI_STRING + "/de"),
+ MimeTypes.APPLICATION_TTML,
+ /* language= */ null,
+ C.SELECTION_FLAG_DEFAULT));
+
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setSubtitles(subtitles).build();
+
+ assertThat(mediaItem.playbackProperties.subtitles).isEqualTo(subtitles);
+ }
+
+ @Test
+ public void builderSetTag_isNullByDefault() {
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
+
+ assertThat(mediaItem.playbackProperties.tag).isNull();
+ }
+
+ @Test
+ public void builderSetTag_setsTag() {
+ Object tag = new Object();
+
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).setTag(tag).build();
+
+ assertThat(mediaItem.playbackProperties.tag).isEqualTo(tag);
+ }
+
+ @Test
+ public void builderSetStartPositionMs_setsStartPositionMs() {
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setClipStartPositionMs(1000L).build();
+
+ assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(1000L);
+ }
+
+ @Test
+ public void builderSetStartPositionMs_zeroByDefault() {
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
+
+ assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(0);
+ }
+
+ @Test
+ public void builderSetStartPositionMs_negativeValue_throws() {
+ MediaItem.Builder builder = new MediaItem.Builder();
+
+ assertThrows(IllegalArgumentException.class, () -> builder.setClipStartPositionMs(-1));
+ }
+
+ @Test
+ public void builderSetEndPositionMs_setsEndPositionMs() {
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setClipEndPositionMs(1000L).build();
+
+ assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(1000L);
+ }
+
+ @Test
+ public void builderSetEndPositionMs_timeEndOfSourceByDefault() {
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
+
+ assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
+ }
+
+ @Test
+ public void builderSetEndPositionMs_timeEndOfSource_setsEndPositionMs() {
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ .setClipEndPositionMs(1000)
+ .setClipEndPositionMs(C.TIME_END_OF_SOURCE)
+ .build();
+
+ assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
+ }
+
+ @Test
+ public void builderSetEndPositionMs_negativeValue_throws() {
+ MediaItem.Builder builder = new MediaItem.Builder();
+
+ assertThrows(IllegalArgumentException.class, () -> builder.setClipEndPositionMs(-1));
+ }
+
+ @Test
+ public void builderSetClippingFlags_setsClippingFlags() {
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_STRING)
+ .setClipRelativeToDefaultPosition(true)
+ .setClipRelativeToLiveWindow(true)
+ .setClipStartsAtKeyFrame(true)
+ .build();
+
+ assertThat(mediaItem.clippingProperties.relativeToDefaultPosition).isTrue();
+ assertThat(mediaItem.clippingProperties.relativeToLiveWindow).isTrue();
+ assertThat(mediaItem.clippingProperties.startsAtKeyFrame).isTrue();
+ }
+
+ @Test
+ public void builderSetMediaMetadata_setsMetadata() {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
+
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setMediaMetadata(mediaMetadata).build();
+
+ assertThat(mediaItem.mediaMetadata).isEqualTo(mediaMetadata);
+ }
+}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java
new file mode 100644
index 0000000..43d5bc5
--- /dev/null
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaMetadataTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link MediaMetadata}. */
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataTest {
+
+ @Test
+ public void builder_minimal_correctDefaults() {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
+
+ assertThat(mediaMetadata.title).isNull();
+ }
+
+ @Test
+ public void builderSetTitle_setsTitle() {
+ String title = "title";
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle(title).build();
+
+ assertThat(mediaMetadata.title).isEqualTo(title);
+ }
+}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/audio/Ac3UtilTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/audio/Ac3UtilTest.java
index 8a7d0b6..26c4214 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/audio/Ac3UtilTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/audio/Ac3UtilTest.java
@@ -33,13 +33,13 @@
Util.getBytesFromHexString("A025048860224E6F6DEDB6D5B6DBAFE6");
@Test
- public void testParseTrueHdSyncframeAudioSampleCount_nonSyncframe() {
+ public void parseTrueHdSyncframeAudioSampleCount_nonSyncframe() {
assertThat(Ac3Util.parseTrueHdSyncframeAudioSampleCount(TRUEHD_NON_SYNCFRAME_HEADER))
.isEqualTo(0);
}
@Test
- public void testParseTrueHdSyncframeAudioSampleCount_syncframe() {
+ public void parseTrueHdSyncframeAudioSampleCount_syncframe() {
assertThat(Ac3Util.parseTrueHdSyncframeAudioSampleCount(TRUEHD_SYNCFRAME_HEADER))
.isEqualTo(TRUEHD_SYNCFRAME_SAMPLE_COUNT);
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/decoder/CryptoInfoTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/decoder/CryptoInfoTest.java
new file mode 100644
index 0000000..2426d47
--- /dev/null
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/decoder/CryptoInfoTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.decoder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link CryptoInfo} */
+@RunWith(AndroidJUnit4.class)
+public class CryptoInfoTest {
+
+ private CryptoInfo cryptoInfo;
+
+ @Before
+ public void setUp() {
+ cryptoInfo = new CryptoInfo();
+ }
+
+ @Test
+ public void increaseClearDataFirstSubSampleBy_numBytesOfClearDataIsNullAndZeroInput_isNoOp() {
+ cryptoInfo.increaseClearDataFirstSubSampleBy(0);
+
+ assertThat(cryptoInfo.numBytesOfClearData).isNull();
+ assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData).isNull();
+ }
+
+ @Test
+ public void increaseClearDataFirstSubSampleBy_withNumBytesOfClearDataSetAndZeroInput_isNoOp() {
+ int[] data = new int[] {1, 1, 1, 1};
+ cryptoInfo.numBytesOfClearData = data;
+ cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = data;
+
+ cryptoInfo.increaseClearDataFirstSubSampleBy(5);
+
+ assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
+ assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(6);
+ }
+
+ @Test
+ public void increaseClearDataFirstSubSampleBy_withSharedClearDataPointer_setsValue() {
+ int[] data = new int[] {1, 1, 1, 1};
+ cryptoInfo.numBytesOfClearData = data;
+ cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = data;
+
+ cryptoInfo.increaseClearDataFirstSubSampleBy(5);
+
+ assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
+ assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(6);
+ }
+}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java
index e3d7cdb..e7b46e5 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java
@@ -47,7 +47,7 @@
TestUtil.buildTestData(128, 3 /* data seed */));
@Test
- public void testParcelable() {
+ public void parcelable() {
DrmInitData drmInitDataToParcel = new DrmInitData(DATA_1, DATA_2);
Parcel parcel = Parcel.obtain();
@@ -61,7 +61,7 @@
}
@Test
- public void testEquals() {
+ public void equals() {
DrmInitData drmInitData = new DrmInitData(DATA_1, DATA_2);
// Basic non-referential equality test.
@@ -95,7 +95,7 @@
@Test
@SuppressWarnings("deprecation")
- public void testGetByUuid() {
+ public void getByUuid() {
// Basic matching.
DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2);
assertThat(testInitData.get(WIDEVINE_UUID)).isEqualTo(DATA_1);
@@ -122,14 +122,14 @@
}
@Test
- public void testGetByIndex() {
+ public void getByIndex() {
DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2);
assertThat(getAllSchemeData(testInitData)).containsAtLeast(DATA_1, DATA_2);
}
@Test
@SuppressWarnings("deprecation")
- public void testSchemeDatasWithSameUuid() {
+ public void schemeDatasWithSameUuid() {
DrmInitData testInitData = new DrmInitData(DATA_1, DATA_1B);
assertThat(testInitData.schemeDataCount).isEqualTo(2);
// Deprecated get method should return first entry.
@@ -140,7 +140,7 @@
}
@Test
- public void testSchemeDataMatches() {
+ public void schemeDataMatches() {
assertThat(DATA_1.matches(WIDEVINE_UUID)).isTrue();
assertThat(DATA_1.matches(PLAYREADY_UUID)).isFalse();
assertThat(DATA_2.matches(UUID_NIL)).isFalse();
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java
index 8331bce..ac3bfdc 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/MetadataTest.java
@@ -28,7 +28,7 @@
public class MetadataTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
Metadata metadataToParcel =
new Metadata(
new BinaryFrame("id1", new byte[] {1}), new BinaryFrame("id2", new byte[] {2}));
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java
index 88a61d0..ee2c55a 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java
@@ -16,13 +16,14 @@
package com.google.android.exoplayer2.metadata.emsg;
import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
import static com.google.android.exoplayer2.testutil.TestUtil.joinByteArrays;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
-import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +32,7 @@
public final class EventMessageDecoderTest {
@Test
- public void testDecodeEventMessage() {
+ public void decodeEventMessage() {
byte[] rawEmsgBody =
joinByteArrays(
createByteArray(117, 114, 110, 58, 116, 101, 115, 116, 0), // scheme_id_uri = "urn:test"
@@ -40,10 +41,8 @@
createByteArray(0, 15, 67, 211), // id = 1000403
createByteArray(0, 1, 2, 3, 4)); // message_data = {0, 1, 2, 3, 4}
EventMessageDecoder decoder = new EventMessageDecoder();
- MetadataInputBuffer buffer = new MetadataInputBuffer();
- buffer.data = ByteBuffer.allocate(rawEmsgBody.length).put(rawEmsgBody);
- Metadata metadata = decoder.decode(buffer);
+ Metadata metadata = decoder.decode(createMetadataInputBuffer(rawEmsgBody));
assertThat(metadata.length()).isEqualTo(1);
EventMessage eventMessage = (EventMessage) metadata.get(0);
@@ -54,4 +53,31 @@
assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4});
}
+ @Test
+ public void decodeEventMessage_failsIfPositionNonZero() {
+ EventMessageDecoder decoder = new EventMessageDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeEventMessage_failsIfBufferHasNoArray() {
+ EventMessageDecoder decoder = new EventMessageDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data = buffer.data.asReadOnlyBuffer();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeEventMessage_failsIfArrayOffsetNonZero() {
+ EventMessageDecoder decoder = new EventMessageDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+ buffer.data = buffer.data.slice();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java
index 5683003..fc73b0c 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.metadata.emsg;
import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
import static com.google.android.exoplayer2.testutil.TestUtil.joinByteArrays;
import static com.google.common.truth.Truth.assertThat;
@@ -23,7 +24,6 @@
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import java.io.IOException;
-import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,18 +43,15 @@
createByteArray(0, 1, 2, 3, 4)); // message_data = {0, 1, 2, 3, 4}
@Test
- public void testEncodeEventStream() throws IOException {
- byte[] foo = new byte[] {1, 2, 3};
-
+ public void encodeEventStream() throws IOException {
byte[] encodedByteArray = new EventMessageEncoder().encode(DECODED_MESSAGE);
assertThat(encodedByteArray).isEqualTo(ENCODED_MESSAGE);
}
@Test
- public void testEncodeDecodeEventStream() throws IOException {
+ public void encodeDecodeEventStream() throws IOException {
byte[] encodedByteArray = new EventMessageEncoder().encode(DECODED_MESSAGE);
- MetadataInputBuffer buffer = new MetadataInputBuffer();
- buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);
+ MetadataInputBuffer buffer = createMetadataInputBuffer(encodedByteArray);
EventMessageDecoder decoder = new EventMessageDecoder();
Metadata metadata = decoder.decode(buffer);
@@ -63,7 +60,7 @@
}
@Test
- public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
+ public void encodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
EventMessage eventMessage1 =
new EventMessage("urn:test", "123", 3000, 1000402, new byte[] {4, 3, 2, 1, 0});
byte[] expectedEmsgBody1 =
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java
index d9e9ab7..9a47b81 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java
@@ -27,7 +27,7 @@
public final class EventMessageTest {
@Test
- public void testEventMessageParcelable() {
+ public void eventMessageParcelable() {
EventMessage eventMessage =
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
// Write to parcel.
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/PictureFrameTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/PictureFrameTest.java
index 3f07dbc..6b7a18e 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/PictureFrameTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/PictureFrameTest.java
@@ -27,7 +27,7 @@
public final class PictureFrameTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
PictureFrame pictureFrameToParcel = new PictureFrame(0, "", "", 0, 0, 0, 0, new byte[0]);
Parcel parcel = Parcel.obtain();
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/VorbisCommentTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/VorbisCommentTest.java
index bb118e3..e3a12a8 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/VorbisCommentTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/flac/VorbisCommentTest.java
@@ -27,7 +27,7 @@
public final class VorbisCommentTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
VorbisComment vorbisCommentFrameToParcel = new VorbisComment("key", "value");
Parcel parcel = Parcel.obtain();
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java
index fbd824c..f3e8838 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java
@@ -27,7 +27,7 @@
public final class ChapterFrameTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
Id3Frame[] subFrames = new Id3Frame[] {
new TextInformationFrame("TIT2", null, "title"),
new UrlLinkFrame("WXXX", "description", "url")
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java
index daf9ff1..0b4a985 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java
@@ -27,7 +27,7 @@
public final class ChapterTocFrameTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
String[] children = new String[] {"child0", "child1"};
Id3Frame[] subFrames = new Id3Frame[] {
new TextInformationFrame("TIT2", null, "title"),
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
index a20cbb6..6389417 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java
@@ -15,11 +15,15 @@
*/
package com.google.android.exoplayer2.metadata.id3;
+import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import java.nio.charset.Charset;
import java.util.Arrays;
@@ -35,7 +39,7 @@
private static final int ID3_TEXT_ENCODING_UTF_8 = 3;
@Test
- public void testDecodeTxxxFrame() {
+ public void decodeTxxxFrame() {
byte[] rawId3 = buildSingleFrameTag("TXXX", new byte[] {3, 0, 109, 100, 105, 97, 108, 111, 103,
95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0});
Id3Decoder decoder = new Id3Decoder();
@@ -62,7 +66,7 @@
}
@Test
- public void testDecodeTextInformationFrame() {
+ public void decodeTextInformationFrame() {
byte[] rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 72, 101, 108, 108, 111, 32, 87, 111,
114, 108, 100, 0});
Id3Decoder decoder = new Id3Decoder();
@@ -89,7 +93,7 @@
}
@Test
- public void testDecodeWxxxFrame() {
+ public void decodeWxxxFrame() {
byte[] rawId3 = buildSingleFrameTag("WXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8, 116, 101, 115,
116, 0, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 115, 116, 46, 99, 111, 109, 47, 97,
98, 99, 63, 100, 101, 102});
@@ -117,7 +121,7 @@
}
@Test
- public void testDecodeUrlLinkFrame() {
+ public void decodeUrlLinkFrame() {
byte[] rawId3 = buildSingleFrameTag("WCOM", new byte[] {104, 116, 116, 112, 115, 58, 47, 47,
116, 101, 115, 116, 46, 99, 111, 109, 47, 97, 98, 99, 63, 100, 101, 102});
Id3Decoder decoder = new Id3Decoder();
@@ -139,7 +143,7 @@
}
@Test
- public void testDecodePrivFrame() {
+ public void decodePrivFrame() {
byte[] rawId3 = buildSingleFrameTag("PRIV", new byte[] {116, 101, 115, 116, 0, 1, 2, 3, 4});
Id3Decoder decoder = new Id3Decoder();
Metadata metadata = decoder.decode(rawId3, rawId3.length);
@@ -158,7 +162,7 @@
}
@Test
- public void testDecodeApicFrame() {
+ public void decodeApicFrame() {
byte[] rawId3 = buildSingleFrameTag("APIC", new byte[] {3, 105, 109, 97, 103, 101, 47, 106, 112,
101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 0});
@@ -174,7 +178,7 @@
}
@Test
- public void testDecodeCommentFrame() {
+ public void decodeCommentFrame() {
byte[] rawId3 = buildSingleFrameTag("COMM", new byte[] {ID3_TEXT_ENCODING_UTF_8, 101, 110, 103,
100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 0, 116, 101, 120, 116, 0});
Id3Decoder decoder = new Id3Decoder();
@@ -201,7 +205,7 @@
}
@Test
- public void testDecodeMultiFrames() {
+ public void decodeMultiFrames() {
byte[] rawId3 =
buildMultiFramesTag(
new FrameSpec(
@@ -233,6 +237,34 @@
assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0});
}
+ @Test
+ public void decodeFailsIfPositionNonZero() {
+ Id3Decoder decoder = new Id3Decoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeFailsIfBufferHasNoArray() {
+ Id3Decoder decoder = new Id3Decoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data = buffer.data.asReadOnlyBuffer();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeFailsIfArrayOffsetNonZero() {
+ Id3Decoder decoder = new Id3Decoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+ buffer.data = buffer.data.slice();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
public static byte[] buildSingleFrameTag(String frameId, byte[] frameData) {
return buildMultiFramesTag(new FrameSpec(frameId, frameData));
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java
index d6bbecd..48e8fce 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java
@@ -27,7 +27,7 @@
public final class MlltFrameTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
MlltFrame mlltFrameToParcel =
new MlltFrame(
/* mpegFramesBetweenReference= */ 1,
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java
new file mode 100644
index 0000000..0c14f32
--- /dev/null
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.offline;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link StreamKey}. */
+@RunWith(AndroidJUnit4.class)
+public class StreamKeyTest {
+
+ @Test
+ public void parcelable() {
+ StreamKey streamKeyToParcel = new StreamKey(1, 2, 3);
+ Parcel parcel = Parcel.obtain();
+ streamKeyToParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ StreamKey streamKeyFromParcel = StreamKey.CREATOR.createFromParcel(parcel);
+ assertThat(streamKeyFromParcel).isEqualTo(streamKeyToParcel);
+
+ parcel.recycle();
+ }
+}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java
index 2323dfe..b3441bb 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/upstream/DataSpecTest.java
@@ -20,7 +20,9 @@
import static junit.framework.TestCase.fail;
import android.net.Uri;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
@@ -30,109 +32,269 @@
@RunWith(AndroidJUnit4.class)
public class DataSpecTest {
+ @SuppressWarnings("deprecation")
@Test
- public void createDataSpec_withDefaultValues_setsEmptyHttpRequestParameters() {
+ public void createDataSpec_withDefaultValues() {
Uri uri = Uri.parse("www.google.com");
+
DataSpec dataSpec = new DataSpec(uri);
+ assertDefaultDataSpec(dataSpec, uri);
- assertThat(dataSpec.httpRequestHeaders.isEmpty()).isTrue();
+ dataSpec = new DataSpec(uri, /* flags= */ 0);
+ assertDefaultDataSpec(dataSpec, uri);
- dataSpec = new DataSpec(uri, /*flags= */ 0);
- assertThat(dataSpec.httpRequestHeaders.isEmpty()).isTrue();
+ dataSpec = new DataSpec(uri, /* position= */ 0, C.LENGTH_UNSET, /* key= */ null);
+ assertDefaultDataSpec(dataSpec, uri);
+
+ dataSpec =
+ new DataSpec(uri, /* position= */ 0, C.LENGTH_UNSET, /* key= */ null, /* flags= */ 0);
+ assertDefaultDataSpec(dataSpec, uri);
dataSpec =
new DataSpec(
uri,
- /* httpMethod= */ 0,
- /* httpBody= */ new byte[] {0, 0, 0, 0},
+ /* position= */ 0,
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0,
+ new HashMap<>());
+ assertDefaultDataSpec(dataSpec, uri);
+
+ dataSpec =
+ new DataSpec(
+ uri,
/* absoluteStreamPosition= */ 0,
/* position= */ 0,
- /* length= */ 1,
- /* key= */ "key",
+ /* length= */ C.LENGTH_UNSET,
+ null,
/* flags= */ 0);
- assertThat(dataSpec.httpRequestHeaders.isEmpty()).isTrue();
+ assertDefaultDataSpec(dataSpec, uri);
+
+ dataSpec =
+ new DataSpec(
+ uri,
+ DataSpec.HTTP_METHOD_GET,
+ /* httpBody= */ null,
+ /* absoluteStreamPosition= */ 0,
+ /* position= */ 0,
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0);
+ assertDefaultDataSpec(dataSpec, uri);
+
+ dataSpec =
+ new DataSpec(
+ uri,
+ DataSpec.HTTP_METHOD_GET,
+ /* httpBody= */ null,
+ /* absoluteStreamPosition= */ 0,
+ /* position= */ 0,
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0,
+ new HashMap<>());
+ assertDefaultDataSpec(dataSpec, uri);
}
@Test
- public void createDataSpec_setsHttpRequestParameters() {
- Map<String, String> httpRequestParameters = new HashMap<>();
- httpRequestParameters.put("key1", "value1");
- httpRequestParameters.put("key2", "value2");
- httpRequestParameters.put("key3", "value3");
+ public void createDataSpec_withBuilder_withDefaultValues() {
+ Uri uri = Uri.parse("www.google.com");
- DataSpec dataSpec =
- new DataSpec(
- Uri.parse("www.google.com"),
- /* httpMethod= */ 0,
- /* httpBody= */ new byte[] {0, 0, 0, 0},
- /* absoluteStreamPosition= */ 0,
- /* position= */ 0,
- /* length= */ 1,
- /* key= */ "key",
- /* flags= */ 0,
- httpRequestParameters);
-
- assertThat(dataSpec.httpRequestHeaders).isEqualTo(httpRequestParameters);
+ DataSpec dataSpec = new DataSpec.Builder().setUri(uri).build();
+ assertDefaultDataSpec(dataSpec, uri);
}
+ @SuppressWarnings("deprecation")
@Test
- public void httpRequestParameters_areReadOnly() {
+ public void createDataSpec_setsValues() {
+ Uri uri = Uri.parse("www.google.com");
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(3);
+ byte[] httpBody = new byte[] {0, 1, 2, 3};
+
DataSpec dataSpec =
new DataSpec(
- Uri.parse("www.google.com"),
- /* httpMethod= */ 0,
- /* httpBody= */ new byte[] {0, 0, 0, 0},
+ uri,
+ DataSpec.HTTP_METHOD_POST,
+ httpBody,
+ /* absoluteStreamPosition= */ 200,
+ /* position= */ 150,
+ /* length= */ 5,
+ /* key= */ "key",
+ /* flags= */ DataSpec.FLAG_ALLOW_GZIP,
+ httpRequestHeaders);
+
+ assertThat(dataSpec.uri).isEqualTo(uri);
+ // uriPositionOffset = absoluteStreamPosition - position
+ assertThat(dataSpec.uriPositionOffset).isEqualTo(50);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_POST);
+ assertThat(dataSpec.httpBody).isEqualTo(httpBody);
+ assertThat(dataSpec.httpRequestHeaders).isEqualTo(httpRequestHeaders);
+ assertThat(dataSpec.absoluteStreamPosition).isEqualTo(200);
+ assertThat(dataSpec.position).isEqualTo(150);
+ assertThat(dataSpec.length).isEqualTo(5);
+ assertThat(dataSpec.key).isEqualTo("key");
+ assertThat(dataSpec.flags).isEqualTo(DataSpec.FLAG_ALLOW_GZIP);
+ assertHttpRequestHeadersReadOnly(dataSpec);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void createDataSpec_withBuilder_setsValues() {
+ Uri uri = Uri.parse("www.google.com");
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(3);
+ byte[] httpBody = new byte[] {0, 1, 2, 3};
+ Object customData = new Object();
+
+ DataSpec dataSpec =
+ new DataSpec.Builder()
+ .setUri(uri)
+ .setUriPositionOffset(50)
+ .setHttpMethod(DataSpec.HTTP_METHOD_POST)
+ .setHttpBody(httpBody)
+ .setPosition(150)
+ .setLength(5)
+ .setKey("key")
+ .setFlags(DataSpec.FLAG_ALLOW_GZIP)
+ .setHttpRequestHeaders(httpRequestHeaders)
+ .setCustomData(customData)
+ .build();
+
+ assertThat(dataSpec.uri).isEqualTo(uri);
+ assertThat(dataSpec.uriPositionOffset).isEqualTo(50);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_POST);
+ assertThat(dataSpec.httpBody).isEqualTo(httpBody);
+ assertThat(dataSpec.httpRequestHeaders).isEqualTo(httpRequestHeaders);
+ // absoluteStreamPosition = uriPositionOffset + position
+ assertThat(dataSpec.absoluteStreamPosition).isEqualTo(200);
+ assertThat(dataSpec.position).isEqualTo(150);
+ assertThat(dataSpec.length).isEqualTo(5);
+ assertThat(dataSpec.key).isEqualTo("key");
+ assertThat(dataSpec.flags).isEqualTo(DataSpec.FLAG_ALLOW_GZIP);
+ assertThat(dataSpec.customData).isEqualTo(customData);
+ assertHttpRequestHeadersReadOnly(dataSpec);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void buildUponDataSpec_setsValues() {
+ Uri uri = Uri.parse("www.google.com");
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(3);
+ byte[] httpBody = new byte[] {0, 1, 2, 3};
+ Object customData = new Object();
+
+ DataSpec dataSpec =
+ new DataSpec.Builder()
+ .setUri(uri)
+ .setUriPositionOffset(50)
+ .setHttpMethod(DataSpec.HTTP_METHOD_POST)
+ .setHttpBody(httpBody)
+ .setPosition(150)
+ .setLength(5)
+ .setKey("key")
+ .setFlags(DataSpec.FLAG_ALLOW_GZIP)
+ .setHttpRequestHeaders(httpRequestHeaders)
+ .setCustomData(customData)
+ .build();
+
+ // Build upon the DataSpec.
+ dataSpec = dataSpec.buildUpon().build();
+
+ assertThat(dataSpec.uri).isEqualTo(uri);
+ assertThat(dataSpec.uriPositionOffset).isEqualTo(50);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_POST);
+ assertThat(dataSpec.httpBody).isEqualTo(httpBody);
+ assertThat(dataSpec.httpRequestHeaders).isEqualTo(httpRequestHeaders);
+ // absoluteStreamPosition = uriPositionOffset + position
+ assertThat(dataSpec.absoluteStreamPosition).isEqualTo(200);
+ assertThat(dataSpec.position).isEqualTo(150);
+ assertThat(dataSpec.length).isEqualTo(5);
+ assertThat(dataSpec.key).isEqualTo("key");
+ assertThat(dataSpec.flags).isEqualTo(DataSpec.FLAG_ALLOW_GZIP);
+ assertThat(dataSpec.customData).isEqualTo(customData);
+ assertHttpRequestHeadersReadOnly(dataSpec);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void createDataSpec_setsHttpMethodAndPostBody() {
+ Uri uri = Uri.parse("www.google.com");
+
+ @Nullable byte[] postBody = new byte[] {0, 1, 2, 3};
+ DataSpec dataSpec =
+ new DataSpec(
+ uri,
+ postBody,
/* absoluteStreamPosition= */ 0,
/* position= */ 0,
- /* length= */ 1,
- /* key= */ "key",
- /* flags= */ 0,
- /* httpRequestHeaders= */ new HashMap<>());
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_POST);
+ assertThat(dataSpec.httpBody).isEqualTo(postBody);
- try {
- dataSpec.httpRequestHeaders.put("key", "value");
- fail();
- } catch (UnsupportedOperationException expected) {
- // Expected
- }
+ postBody = new byte[0];
+ dataSpec =
+ new DataSpec(
+ uri,
+ postBody,
+ /* absoluteStreamPosition= */ 0,
+ /* position= */ 0,
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_POST);
+ assertThat(dataSpec.httpBody).isNull();
+
+ postBody = null;
+ dataSpec =
+ new DataSpec(
+ uri,
+ postBody,
+ /* absoluteStreamPosition= */ 0,
+ /* position= */ 0,
+ /* length= */ C.LENGTH_UNSET,
+ /* key= */ null,
+ /* flags= */ 0);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_GET);
+ assertThat(dataSpec.httpBody).isNull();
}
@Test
public void withUri_copiesHttpRequestHeaders() {
- Map<String, String> httpRequestProperties = createRequestProperties(5);
- DataSpec dataSpec = createDataSpecWithHeaders(httpRequestProperties);
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(5);
+ DataSpec dataSpec = createDataSpecWithHttpRequestHeaders(httpRequestHeaders);
DataSpec dataSpecCopy = dataSpec.withUri(Uri.parse("www.new-uri.com"));
- assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestProperties);
+ assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestHeaders);
}
@Test
public void subrange_copiesHttpRequestHeaders() {
- Map<String, String> httpRequestProperties = createRequestProperties(5);
- DataSpec dataSpec = createDataSpecWithHeaders(httpRequestProperties);
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(5);
+ DataSpec dataSpec = createDataSpecWithHttpRequestHeaders(httpRequestHeaders);
DataSpec dataSpecCopy = dataSpec.subrange(2);
- assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestProperties);
+ assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestHeaders);
}
@Test
public void subrange_withOffsetAndLength_copiesHttpRequestHeaders() {
- Map<String, String> httpRequestProperties = createRequestProperties(5);
- DataSpec dataSpec = createDataSpecWithHeaders(httpRequestProperties);
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(5);
+ DataSpec dataSpec = createDataSpecWithHttpRequestHeaders(httpRequestHeaders);
DataSpec dataSpecCopy = dataSpec.subrange(2, 2);
- assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestProperties);
+ assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(httpRequestHeaders);
}
@Test
public void withRequestHeaders_setsCorrectHeaders() {
- Map<String, String> httpRequestProperties = createRequestProperties(5);
- DataSpec dataSpec = createDataSpecWithHeaders(httpRequestProperties);
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(5);
+ DataSpec dataSpec = createDataSpecWithHttpRequestHeaders(httpRequestHeaders);
- Map<String, String> newRequestHeaders = createRequestProperties(5, 10);
+ Map<String, String> newRequestHeaders = createHttpRequestHeaders(5, 10);
DataSpec dataSpecCopy = dataSpec.withRequestHeaders(newRequestHeaders);
assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(newRequestHeaders);
@@ -140,13 +302,13 @@
@Test
public void withAdditionalHeaders_setsCorrectHeaders() {
- Map<String, String> httpRequestProperties = createRequestProperties(5);
- DataSpec dataSpec = createDataSpecWithHeaders(httpRequestProperties);
- Map<String, String> additionalHeaders = createRequestProperties(5, 10);
+ Map<String, String> httpRequestHeaders = createHttpRequestHeaders(5);
+ DataSpec dataSpec = createDataSpecWithHttpRequestHeaders(httpRequestHeaders);
+ Map<String, String> additionalHeaders = createHttpRequestHeaders(5, 10);
// additionalHeaders may overwrite a header key
- String existingKey = httpRequestProperties.keySet().iterator().next();
+ String existingKey = httpRequestHeaders.keySet().iterator().next();
additionalHeaders.put(existingKey, "overwritten");
- Map<String, String> expectedHeaders = new HashMap<>(httpRequestProperties);
+ Map<String, String> expectedHeaders = new HashMap<>(httpRequestHeaders);
expectedHeaders.putAll(additionalHeaders);
DataSpec dataSpecCopy = dataSpec.withAdditionalHeaders(additionalHeaders);
@@ -154,11 +316,11 @@
assertThat(dataSpecCopy.httpRequestHeaders).isEqualTo(expectedHeaders);
}
- private static Map<String, String> createRequestProperties(int howMany) {
- return createRequestProperties(0, howMany);
+ private static Map<String, String> createHttpRequestHeaders(int howMany) {
+ return createHttpRequestHeaders(0, howMany);
}
- private static Map<String, String> createRequestProperties(int from, int to) {
+ private static Map<String, String> createHttpRequestHeaders(int from, int to) {
assertThat(from).isLessThan(to);
Map<String, String> httpRequestParameters = new HashMap<>();
@@ -169,16 +331,36 @@
return httpRequestParameters;
}
- private static DataSpec createDataSpecWithHeaders(Map<String, String> httpRequestProperties) {
- return new DataSpec(
- Uri.parse("www.google.com"),
- /* httpMethod= */ 0,
- /* httpBody= */ new byte[] {0, 0, 0, 0},
- /* absoluteStreamPosition= */ 0,
- /* position= */ 0,
- /* length= */ 1,
- /* key= */ "key",
- /* flags= */ 0,
- httpRequestProperties);
+ private static DataSpec createDataSpecWithHttpRequestHeaders(
+ Map<String, String> httpRequestHeaders) {
+ return new DataSpec.Builder()
+ .setUri("www.google.com")
+ .setHttpRequestHeaders(httpRequestHeaders)
+ .build();
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void assertDefaultDataSpec(DataSpec dataSpec, Uri uri) {
+ assertThat(dataSpec.uri).isEqualTo(uri);
+ assertThat(dataSpec.uriPositionOffset).isEqualTo(0);
+ assertThat(dataSpec.httpMethod).isEqualTo(DataSpec.HTTP_METHOD_GET);
+ assertThat(dataSpec.httpBody).isNull();
+ assertThat(dataSpec.httpRequestHeaders).isEmpty();
+ assertThat(dataSpec.absoluteStreamPosition).isEqualTo(0);
+ assertThat(dataSpec.position).isEqualTo(0);
+ assertThat(dataSpec.length).isEqualTo(C.LENGTH_UNSET);
+ assertThat(dataSpec.key).isNull();
+ assertThat(dataSpec.flags).isEqualTo(0);
+ assertThat(dataSpec.customData).isNull();
+ assertHttpRequestHeadersReadOnly(dataSpec);
+ }
+
+ private static void assertHttpRequestHeadersReadOnly(DataSpec dataSpec) {
+ try {
+ dataSpec.httpRequestHeaders.put("key", "value");
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ // Expected
+ }
}
}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java
new file mode 100644
index 0000000..92e4124
--- /dev/null
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/CopyOnWriteMultisetTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.Iterator;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link CopyOnWriteMultiset}. */
+@RunWith(AndroidJUnit4.class)
+public final class CopyOnWriteMultisetTest {
+
+ @Test
+ public void multipleEqualObjectsCountedAsExpected() {
+ String item1 = "a string";
+ String item2 = "a string";
+ String item3 = "different string";
+
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+
+ multiset.add(item1);
+ multiset.add(item2);
+ multiset.add(item3);
+
+ assertThat(multiset).containsExactly("a string", "a string", "different string");
+ assertThat(multiset.elementSet()).containsExactly("a string", "different string");
+ }
+
+ @Test
+ public void removingObjectDecrementsCount() {
+ String item1 = "a string";
+ String item2 = "a string";
+ String item3 = "different string";
+
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+
+ multiset.add(item1);
+ multiset.add(item2);
+ multiset.add(item3);
+
+ multiset.remove("a string");
+
+ assertThat(multiset).containsExactly("a string", "different string");
+ assertThat(multiset.elementSet()).containsExactly("a string", "different string");
+ }
+
+ @Test
+ public void removingLastObjectRemovesCompletely() {
+ String item1 = "a string";
+ String item2 = "a string";
+ String item3 = "different string";
+
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+
+ multiset.add(item1);
+ multiset.add(item2);
+ multiset.add(item3);
+
+ multiset.remove("different string");
+
+ assertThat(multiset).containsExactly("a string", "a string");
+ assertThat(multiset.elementSet()).containsExactly("a string");
+ }
+
+ @Test
+ public void removingNonexistentElementSucceeds() {
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+
+ multiset.remove("a string");
+ }
+
+ @Test
+ public void modifyingIteratorFails() {
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+ multiset.add("a string");
+
+ Iterator<String> iterator = multiset.iterator();
+
+ assertThrows(UnsupportedOperationException.class, iterator::remove);
+ }
+
+ @Test
+ public void modifyingElementSetFails() {
+ CopyOnWriteMultiset<String> multiset = new CopyOnWriteMultiset<>();
+ multiset.add("a string");
+
+ Set<String> elementSet = multiset.elementSet();
+
+ assertThrows(UnsupportedOperationException.class, () -> elementSet.remove("a string"));
+ }
+}
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java
index 288ad91..e88385b 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java
@@ -26,7 +26,30 @@
public final class MimeTypesTest {
@Test
- public void testGetMediaMimeType_fromValidCodecs_returnsCorrectMimeType() {
+ public void isText_returnsCorrectResult() {
+ assertThat(MimeTypes.isText(MimeTypes.TEXT_VTT)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.TEXT_SSA)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_CEA608)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_CEA708)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_MP4CEA608)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_SUBRIP)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_TTML)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_TX3G)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_MP4VTT)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_VOBSUB)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_PGS)).isTrue();
+ assertThat(MimeTypes.isText(MimeTypes.APPLICATION_DVBSUBS)).isTrue();
+ assertThat(MimeTypes.isText("text/custom")).isTrue();
+
+ assertThat(MimeTypes.isText(MimeTypes.VIDEO_MP4)).isFalse();
+ assertThat(MimeTypes.isText(MimeTypes.VIDEO_H264)).isFalse();
+ assertThat(MimeTypes.isText(MimeTypes.AUDIO_MP4)).isFalse();
+ assertThat(MimeTypes.isText(MimeTypes.AUDIO_AAC)).isFalse();
+ assertThat(MimeTypes.isText("application/custom")).isFalse();
+ }
+
+ @Test
+ public void getMediaMimeType_fromValidCodecs_returnsCorrectMimeType() {
assertThat(MimeTypes.getMediaMimeType("avc1")).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(MimeTypes.getMediaMimeType("avc1.42E01E")).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(MimeTypes.getMediaMimeType("avc1.42E01F")).isEqualTo(MimeTypes.VIDEO_H264);
@@ -73,10 +96,17 @@
assertThat(MimeTypes.getMediaMimeType("mp4a.AA")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
assertThat(MimeTypes.getMediaMimeType("mp4a.AB")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
assertThat(MimeTypes.getMediaMimeType("mp4a.AD")).isEqualTo(MimeTypes.AUDIO_OPUS);
+
+ assertThat(MimeTypes.getMediaMimeType("wvtt")).isEqualTo(MimeTypes.TEXT_VTT);
+ assertThat(MimeTypes.getMediaMimeType("stpp.")).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(MimeTypes.getMediaMimeType("stpp.ttml.im1t")).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(MimeTypes.getMediaMimeType("eia608.")).isEqualTo(MimeTypes.APPLICATION_CEA608);
+ assertThat(MimeTypes.getMediaMimeType("cea608")).isEqualTo(MimeTypes.APPLICATION_CEA608);
+ assertThat(MimeTypes.getMediaMimeType("cea708")).isEqualTo(MimeTypes.APPLICATION_CEA708);
}
@Test
- public void testGetMimeTypeFromMp4ObjectType_forValidObjectType_returnsCorrectMimeType() {
+ public void getMimeTypeFromMp4ObjectType_forValidObjectType_returnsCorrectMimeType() {
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x60)).isEqualTo(MimeTypes.VIDEO_MPEG2);
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x61)).isEqualTo(MimeTypes.VIDEO_MPEG2);
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x20)).isEqualTo(MimeTypes.VIDEO_MP4V);
@@ -97,7 +127,7 @@
}
@Test
- public void testGetMimeTypeFromMp4ObjectType_forInvalidObjectType_returnsNull() {
+ public void getMimeTypeFromMp4ObjectType_forInvalidObjectType_returnsNull() {
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0)).isNull();
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x600)).isNull();
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x01)).isNull();
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java
index 2cc26fe..365cff8 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java
@@ -36,7 +36,7 @@
private static final int SPS_TEST_DATA_OFFSET = 3;
@Test
- public void testFindNalUnit() {
+ public void findNalUnit() {
byte[] data = buildTestData();
// Should find NAL unit.
@@ -57,7 +57,7 @@
}
@Test
- public void testFindNalUnitWithPrefix() {
+ public void findNalUnitWithPrefix() {
byte[] data = buildTestData();
// First byte of NAL unit in data1, rest in data2.
@@ -121,7 +121,7 @@
}
@Test
- public void testParseSpsNalUnit() {
+ public void parseSpsNalUnit() {
NalUnitUtil.SpsData data = NalUnitUtil.parseSpsNalUnit(SPS_TEST_DATA, SPS_TEST_DATA_OFFSET,
SPS_TEST_DATA.length);
assertThat(data.width).isEqualTo(640);
@@ -137,7 +137,7 @@
}
@Test
- public void testUnescapeDoesNotModifyBuffersWithoutStartCodes() {
+ public void unescapeDoesNotModifyBuffersWithoutStartCodes() {
assertUnescapeDoesNotModify("");
assertUnescapeDoesNotModify("0000");
assertUnescapeDoesNotModify("172BF38A3C");
@@ -145,13 +145,13 @@
}
@Test
- public void testUnescapeModifiesBuffersWithStartCodes() {
+ public void unescapeModifiesBuffersWithStartCodes() {
assertUnescapeMatchesExpected("00000301", "000001");
assertUnescapeMatchesExpected("0000030200000300", "000002000000");
}
@Test
- public void testDiscardToSps() {
+ public void discardToSps() {
assertDiscardToSpsMatchesExpected("", "");
assertDiscardToSpsMatchesExpected("00", "");
assertDiscardToSpsMatchesExpected("FFFF000001", "");
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java
index f031468..9a2d17c 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java
@@ -16,9 +16,12 @@
package com.google.android.exoplayer2.util;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil;
+import java.nio.charset.Charset;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,7 +30,7 @@
public final class ParsableBitArrayTest {
@Test
- public void testReadAllBytes() {
+ public void readAllBytes() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F);
ParsableBitArray testArray = new ParsableBitArray(testData);
byte[] bytesRead = new byte[testData.length];
@@ -40,7 +43,7 @@
}
@Test
- public void testReadBitInSameByte() {
+ public void readBitInSameByte() {
byte[] testData = TestUtil.createByteArray(0, 0b00110000);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.setPosition(10);
@@ -52,7 +55,7 @@
}
@Test
- public void testReadBitInMultipleBytes() {
+ public void readBitInMultipleBytes() {
byte[] testData = TestUtil.createByteArray(1, 1 << 7);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.setPosition(6);
@@ -64,7 +67,7 @@
}
@Test
- public void testReadBits0Bits() {
+ public void readBits0Bits() {
byte[] testData = TestUtil.createByteArray(0x3C);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -74,7 +77,7 @@
}
@Test
- public void testReadBitsByteAligned() {
+ public void readBitsByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.readBits(8);
@@ -86,7 +89,7 @@
}
@Test
- public void testReadBitsNonByteAligned() {
+ public void readBitsNonByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.readBits(3);
@@ -98,7 +101,7 @@
}
@Test
- public void testReadBitsNegativeValue() {
+ public void readBitsNegativeValue() {
byte[] testData = TestUtil.createByteArray(0xF0, 0, 0, 0);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -108,7 +111,7 @@
}
@Test
- public void testReadBitsToLong0Bits() {
+ public void readBitsToLong0Bits() {
byte[] testData = TestUtil.createByteArray(0x3C);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -118,7 +121,7 @@
}
@Test
- public void testReadBitsToLongByteAligned() {
+ public void readBitsToLongByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01, 0xFF, 0x14, 0x60);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.readBits(8);
@@ -130,7 +133,7 @@
}
@Test
- public void testReadBitsToLongNonByteAligned() {
+ public void readBitsToLongNonByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01, 0xFF, 0x14, 0x60);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.readBits(3);
@@ -142,7 +145,7 @@
}
@Test
- public void testReadBitsToLongNegativeValue() {
+ public void readBitsToLongNegativeValue() {
byte[] testData = TestUtil.createByteArray(0xF0, 0, 0, 0, 0, 0, 0, 0);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -152,7 +155,7 @@
}
@Test
- public void testReadBitsToByteArray() {
+ public void readBitsToByteArray() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01, 0xFF, 0x14, 0x60, 0x99);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -201,7 +204,7 @@
}
@Test
- public void testSkipBytes() {
+ public void skipBytes() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -211,7 +214,7 @@
}
@Test
- public void testSkipBitsByteAligned() {
+ public void skipBitsByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -221,7 +224,7 @@
}
@Test
- public void testSkipBitsNonByteAligned() {
+ public void skipBitsNonByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -231,7 +234,7 @@
}
@Test
- public void testSetPositionByteAligned() {
+ public void setPositionByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -241,7 +244,7 @@
}
@Test
- public void testSetPositionNonByteAligned() {
+ public void setPositionNonByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
@@ -251,7 +254,7 @@
}
@Test
- public void testByteAlignFromNonByteAligned() {
+ public void byteAlignFromNonByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.setPosition(11);
@@ -264,7 +267,7 @@
}
@Test
- public void testByteAlignFromByteAligned() {
+ public void byteAlignFromByteAligned() {
byte[] testData = TestUtil.createByteArray(0x3C, 0xD2, 0x5F, 0x01);
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.setPosition(16);
@@ -277,7 +280,36 @@
}
@Test
- public void testPutBitsWithinByte() {
+ public void readBytesAsStringDefaultsToUtf8() {
+ byte[] testData = "a non-åscii strìng".getBytes(Charset.forName(C.UTF8_NAME));
+ ParsableBitArray testArray = new ParsableBitArray(testData);
+
+ testArray.skipBytes(2);
+ assertThat(testArray.readBytesAsString(testData.length - 2)).isEqualTo("non-åscii strìng");
+ }
+
+ @Test
+ public void readBytesAsStringExplicitCharset() {
+ byte[] testData = "a non-åscii strìng".getBytes(Charset.forName(C.UTF16_NAME));
+ ParsableBitArray testArray = new ParsableBitArray(testData);
+
+ testArray.skipBytes(6);
+ assertThat(testArray.readBytesAsString(testData.length - 6, Charset.forName(C.UTF16_NAME)))
+ .isEqualTo("non-åscii strìng");
+ }
+
+ @Test
+ public void readBytesNotByteAligned() {
+ String testString = "test string";
+ byte[] testData = testString.getBytes(Charset.forName(C.UTF8_NAME));
+ ParsableBitArray testArray = new ParsableBitArray(testData);
+
+ testArray.skipBit();
+ assertThrows(IllegalStateException.class, () -> testArray.readBytesAsString(2));
+ }
+
+ @Test
+ public void putBitsWithinByte() {
ParsableBitArray output = new ParsableBitArray(new byte[4]);
output.skipBits(1);
@@ -288,7 +320,7 @@
}
@Test
- public void testPutBitsAcrossTwoBytes() {
+ public void putBitsAcrossTwoBytes() {
ParsableBitArray output = new ParsableBitArray(new byte[4]);
output.setPosition(12);
@@ -299,7 +331,7 @@
}
@Test
- public void testPutBitsAcrossMultipleBytes() {
+ public void putBitsAcrossMultipleBytes() {
ParsableBitArray output = new ParsableBitArray(new byte[8]);
output.setPosition(31); // Writing starts at 31 to test the 30th bit is not modified.
@@ -310,7 +342,7 @@
}
@Test
- public void testPut32Bits() {
+ public void put32Bits() {
ParsableBitArray output = new ParsableBitArray(new byte[5]);
output.setPosition(4);
@@ -321,7 +353,7 @@
}
@Test
- public void testPutFullBytes() {
+ public void putFullBytes() {
ParsableBitArray output = new ParsableBitArray(new byte[2]);
output.putInt(0x81, 8);
@@ -331,7 +363,7 @@
}
@Test
- public void testNoOverwriting() {
+ public void noOverwriting() {
ParsableBitArray output =
new ParsableBitArray(TestUtil.createByteArray(0xFF, 0xFF, 0xFF, 0xFF, 0xFF));
output.setPosition(1);
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java
index 7b441b8..894de47 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java
@@ -39,7 +39,7 @@
}
@Test
- public void testReadShort() {
+ public void readShort() {
testReadShort((short) -1);
testReadShort((short) 0);
testReadShort((short) 1);
@@ -65,7 +65,7 @@
}
@Test
- public void testReadInt() {
+ public void readInt() {
testReadInt(0);
testReadInt(1);
testReadInt(-1);
@@ -91,7 +91,7 @@
}
@Test
- public void testReadUnsignedInt() {
+ public void readUnsignedInt() {
testReadUnsignedInt(0);
testReadUnsignedInt(1);
testReadUnsignedInt(Integer.MAX_VALUE);
@@ -117,7 +117,7 @@
}
@Test
- public void testReadUnsignedIntToInt() {
+ public void readUnsignedIntToInt() {
testReadUnsignedIntToInt(0);
testReadUnsignedIntToInt(1);
testReadUnsignedIntToInt(Integer.MAX_VALUE);
@@ -153,7 +153,7 @@
}
@Test
- public void testReadUnsignedLongToLong() {
+ public void readUnsignedLongToLong() {
testReadUnsignedLongToLong(0);
testReadUnsignedLongToLong(1);
testReadUnsignedLongToLong(Long.MAX_VALUE);
@@ -189,7 +189,7 @@
}
@Test
- public void testReadLong() {
+ public void readLong() {
testReadLong(0);
testReadLong(1);
testReadLong(-1);
@@ -215,7 +215,7 @@
}
@Test
- public void testReadingMovesPosition() {
+ public void readingMovesPosition() {
ParsableByteArray parsableByteArray = getTestDataArray();
// Given an array at the start
@@ -226,7 +226,7 @@
}
@Test
- public void testOutOfBoundsThrows() {
+ public void outOfBoundsThrows() {
ParsableByteArray parsableByteArray = getTestDataArray();
// Given an array at the end
@@ -242,7 +242,7 @@
}
@Test
- public void testModificationsAffectParsableArray() {
+ public void modificationsAffectParsableArray() {
ParsableByteArray parsableByteArray = getTestDataArray();
// When modifying the wrapped byte array
@@ -255,7 +255,7 @@
}
@Test
- public void testReadingUnsignedLongWithMsbSetThrows() {
+ public void readingUnsignedLongWithMsbSetThrows() {
ParsableByteArray parsableByteArray = getTestDataArray();
// Given an array with the most-significant bit set on the top byte
@@ -271,7 +271,7 @@
}
@Test
- public void testReadUnsignedFixedPoint1616() {
+ public void readUnsignedFixedPoint1616() {
ParsableByteArray parsableByteArray = getTestDataArray();
// When reading the integer part of a 16.16 fixed point value
@@ -282,7 +282,7 @@
}
@Test
- public void testReadingBytesReturnsCopy() {
+ public void readingBytesReturnsCopy() {
ParsableByteArray parsableByteArray = getTestDataArray();
// When reading all the bytes back
@@ -295,7 +295,7 @@
}
@Test
- public void testReadLittleEndianLong() {
+ public void readLittleEndianLong() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, (byte) 0xFF
@@ -305,7 +305,7 @@
}
@Test
- public void testReadLittleEndianUnsignedInt() {
+ public void readLittleEndianUnsignedInt() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x10, 0x00, 0x00, (byte) 0xFF
});
@@ -314,7 +314,7 @@
}
@Test
- public void testReadLittleEndianInt() {
+ public void readLittleEndianInt() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, 0x00, 0x00, (byte) 0xFF
});
@@ -323,7 +323,7 @@
}
@Test
- public void testReadLittleEndianUnsignedInt24() {
+ public void readLittleEndianUnsignedInt24() {
byte[] data = {0x01, 0x02, (byte) 0xFF};
ParsableByteArray byteArray = new ParsableByteArray(data);
assertThat(byteArray.readLittleEndianUnsignedInt24()).isEqualTo(0xFF0201);
@@ -331,7 +331,7 @@
}
@Test
- public void testReadInt24Positive() {
+ public void readInt24Positive() {
byte[] data = {0x01, 0x02, (byte) 0xFF};
ParsableByteArray byteArray = new ParsableByteArray(data);
assertThat(byteArray.readInt24()).isEqualTo(0x0102FF);
@@ -339,7 +339,7 @@
}
@Test
- public void testReadInt24Negative() {
+ public void readInt24Negative() {
byte[] data = {(byte) 0xFF, 0x02, (byte) 0x01};
ParsableByteArray byteArray = new ParsableByteArray(data);
assertThat(byteArray.readInt24()).isEqualTo(0xFFFF0201);
@@ -347,7 +347,7 @@
}
@Test
- public void testReadLittleEndianUnsignedShort() {
+ public void readLittleEndianUnsignedShort() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, (byte) 0xFF, 0x02, (byte) 0xFF
});
@@ -358,7 +358,7 @@
}
@Test
- public void testReadLittleEndianShort() {
+ public void readLittleEndianShort() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, (byte) 0xFF, 0x02, (byte) 0xFF
});
@@ -369,7 +369,7 @@
}
@Test
- public void testReadString() {
+ public void readString() {
byte[] data = {
(byte) 0xC3, (byte) 0xA4, (byte) 0x20,
(byte) 0xC3, (byte) 0xB6, (byte) 0x20,
@@ -385,7 +385,7 @@
}
@Test
- public void testReadAsciiString() {
+ public void readAsciiString() {
byte[] data = new byte[] {'t', 'e', 's', 't'};
ParsableByteArray testArray = new ParsableByteArray(data);
assertThat(testArray.readString(data.length, forName("US-ASCII"))).isEqualTo("test");
@@ -393,7 +393,7 @@
}
@Test
- public void testReadStringOutOfBoundsDoesNotMovePosition() {
+ public void readStringOutOfBoundsDoesNotMovePosition() {
byte[] data = {
(byte) 0xC3, (byte) 0xA4, (byte) 0x20
};
@@ -407,14 +407,14 @@
}
@Test
- public void testReadEmptyString() {
+ public void readEmptyString() {
byte[] bytes = new byte[0];
ParsableByteArray parser = new ParsableByteArray(bytes);
assertThat(parser.readLine()).isNull();
}
@Test
- public void testReadNullTerminatedStringWithLengths() {
+ public void readNullTerminatedStringWithLengths() {
byte[] bytes = new byte[] {
'f', 'o', 'o', 0, 'b', 'a', 'r', 0
};
@@ -449,7 +449,7 @@
}
@Test
- public void testReadNullTerminatedString() {
+ public void readNullTerminatedString() {
byte[] bytes = new byte[] {
'f', 'o', 'o', 0, 'b', 'a', 'r', 0
};
@@ -473,7 +473,7 @@
}
@Test
- public void testReadNullTerminatedStringWithoutEndingNull() {
+ public void readNullTerminatedStringWithoutEndingNull() {
byte[] bytes = new byte[] {
'f', 'o', 'o', 0, 'b', 'a', 'r'
};
@@ -484,7 +484,7 @@
}
@Test
- public void testReadSingleLineWithoutEndingTrail() {
+ public void readSingleLineWithoutEndingTrail() {
byte[] bytes = new byte[] {
'f', 'o', 'o'
};
@@ -494,7 +494,7 @@
}
@Test
- public void testReadSingleLineWithEndingLf() {
+ public void readSingleLineWithEndingLf() {
byte[] bytes = new byte[] {
'f', 'o', 'o', '\n'
};
@@ -504,7 +504,7 @@
}
@Test
- public void testReadTwoLinesWithCrFollowedByLf() {
+ public void readTwoLinesWithCrFollowedByLf() {
byte[] bytes = new byte[] {
'f', 'o', 'o', '\r', '\n', 'b', 'a', 'r'
};
@@ -515,7 +515,7 @@
}
@Test
- public void testReadThreeLinesWithEmptyLine() {
+ public void readThreeLinesWithEmptyLine() {
byte[] bytes = new byte[] {
'f', 'o', 'o', '\r', '\n', '\r', 'b', 'a', 'r'
};
@@ -527,7 +527,7 @@
}
@Test
- public void testReadFourLinesWithLfFollowedByCr() {
+ public void readFourLinesWithLfFollowedByCr() {
byte[] bytes = new byte[] {
'f', 'o', 'o', '\n', '\r', '\r', 'b', 'a', 'r', '\r', '\n'
};
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java
index 3940c3d..8fffb9a 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java
@@ -32,7 +32,7 @@
private static final byte[] MIX_TEST_DATA = createByteArray(255, 0, 0, 3, 255, 0, 0, 127);
@Test
- public void testReadNoEscaping() {
+ public void readNoEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(NO_ESCAPING_TEST_DATA, 0, NO_ESCAPING_TEST_DATA.length);
assertThat(array.readBits(24)).isEqualTo(0x000300);
@@ -44,7 +44,7 @@
}
@Test
- public void testReadNoEscapingTruncated() {
+ public void readNoEscapingTruncated() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(NO_ESCAPING_TEST_DATA, 0, 4);
assertThat(array.canReadBits(32)).isTrue();
array.skipBits(32);
@@ -58,7 +58,7 @@
}
@Test
- public void testReadAllEscaping() {
+ public void readAllEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(ALL_ESCAPING_TEST_DATA, 0, ALL_ESCAPING_TEST_DATA.length);
assertThat(array.canReadBits(48)).isTrue();
@@ -70,7 +70,7 @@
}
@Test
- public void testReadMix() {
+ public void readMix() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(MIX_TEST_DATA, 0, MIX_TEST_DATA.length);
assertThat(array.canReadBits(56)).isTrue();
@@ -84,7 +84,7 @@
}
@Test
- public void testReadExpGolomb() {
+ public void readExpGolomb() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(createByteArray(0x9E), 0, 1);
assertThat(array.canReadExpGolombCodedNum()).isTrue();
assertThat(array.readUnsignedExpGolombCodedInt()).isEqualTo(0);
@@ -100,7 +100,7 @@
}
@Test
- public void testReadExpGolombWithEscaping() {
+ public void readExpGolombWithEscaping() {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(createByteArray(0, 0, 3, 128, 0), 0, 5);
assertThat(array.canReadExpGolombCodedNum()).isFalse();
@@ -111,7 +111,7 @@
}
@Test
- public void testReset() {
+ public void reset() {
ParsableNalUnitBitArray array = new ParsableNalUnitBitArray(createByteArray(0, 0), 0, 2);
assertThat(array.canReadExpGolombCodedNum()).isFalse();
assertThat(array.canReadBits(16)).isTrue();
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java
index f38ffab..825988c 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java
@@ -40,7 +40,7 @@
public class UtilTest {
@Test
- public void testAddWithOverflowDefault() {
+ public void addWithOverflowDefault_withoutOverFlow_returnsSum() {
long res = Util.addWithOverflowDefault(5, 10, /* overflowResult= */ 0);
assertThat(res).isEqualTo(15);
@@ -49,8 +49,11 @@
res = Util.addWithOverflowDefault(Long.MIN_VALUE + 1, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MIN_VALUE);
+ }
- res = Util.addWithOverflowDefault(Long.MAX_VALUE, 1, /* overflowResult= */ 12345);
+ @Test
+ public void addWithOverflowDefault_withOverFlow_returnsOverflowDefault() {
+ long res = Util.addWithOverflowDefault(Long.MAX_VALUE, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
res = Util.addWithOverflowDefault(Long.MIN_VALUE, -1, /* overflowResult= */ 12345);
@@ -58,7 +61,7 @@
}
@Test
- public void testSubtrackWithOverflowDefault() {
+ public void subtrackWithOverflowDefault_withoutUnderflow_returnsSubtract() {
long res = Util.subtractWithOverflowDefault(5, 10, /* overflowResult= */ 0);
assertThat(res).isEqualTo(-5);
@@ -67,8 +70,11 @@
res = Util.subtractWithOverflowDefault(Long.MAX_VALUE - 1, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MAX_VALUE);
+ }
- res = Util.subtractWithOverflowDefault(Long.MIN_VALUE, 1, /* overflowResult= */ 12345);
+ @Test
+ public void subtrackWithOverflowDefault_withUnderflow_returnsOverflowDefault() {
+ long res = Util.subtractWithOverflowDefault(Long.MIN_VALUE, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
res = Util.subtractWithOverflowDefault(Long.MAX_VALUE, -1, /* overflowResult= */ 12345);
@@ -76,7 +82,7 @@
}
@Test
- public void testInferContentType() {
+ public void inferContentType_returnsInferredResult() {
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS);
@@ -215,130 +221,94 @@
@Test
public void
longArrayBinarySearchFloor_targetSmallerThanValuesAndStayInBoundsFalse_returnsMinus1() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 0, /* inclusive= */ false, /* stayInBounds= */ false))
+ newLongArray(1, 3, 5),
+ /* value= */ 0,
+ /* inclusive= */ false,
+ /* stayInBounds= */ false))
.isEqualTo(-1);
}
@Test
public void longArrayBinarySearchFloor_targetSmallerThanValuesAndStayInBoundsTrue_returns0() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 0, /* inclusive= */ false, /* stayInBounds= */ true))
+ newLongArray(1, 3, 5),
+ /* value= */ 0,
+ /* inclusive= */ false,
+ /* stayInBounds= */ true))
.isEqualTo(0);
}
@Test
public void longArrayBinarySearchFloor_targetBiggerThanValues_returnsLastIndex() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 6, /* inclusive= */ false, /* stayInBounds= */ false))
+ newLongArray(1, 3, 5),
+ /* value= */ 6,
+ /* inclusive= */ false,
+ /* stayInBounds= */ false))
.isEqualTo(2);
}
@Test
public void
longArrayBinarySearchFloor_targetEqualToFirstValueAndInclusiveFalseAndStayInBoundsFalse_returnsMinus1() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 1, /* inclusive= */ false, /* stayInBounds= */ false))
+ newLongArray(1, 1, 1, 1, 1, 3, 5),
+ /* value= */ 1,
+ /* inclusive= */ false,
+ /* stayInBounds= */ false))
.isEqualTo(-1);
}
@Test
public void
longArrayBinarySearchFloor_targetEqualToFirstValueAndInclusiveFalseAndStayInBoundsTrue_returns0() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 1, /* inclusive= */ false, /* stayInBounds= */ true))
+ newLongArray(1, 1, 1, 1, 1, 3, 5),
+ /* value= */ 1,
+ /* inclusive= */ false,
+ /* stayInBounds= */ true))
.isEqualTo(0);
}
@Test
public void
longArrayBinarySearchFloor_targetInArrayAndInclusiveTrue_returnsFirstIndexWithValueEqualToTarget() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 1, /* inclusive= */ true, /* stayInBounds= */ false))
+ newLongArray(1, 1, 1, 1, 1, 3, 5),
+ /* value= */ 1,
+ /* inclusive= */ true,
+ /* stayInBounds= */ false))
.isEqualTo(0);
}
@Test
public void
longArrayBinarySearchFloor_targetBetweenValuesAndInclusiveFalse_returnsIndexWhereTargetShouldBeInserted() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 2, /* inclusive= */ false, /* stayInBounds= */ false))
+ newLongArray(1, 1, 1, 1, 1, 3, 5),
+ /* value= */ 2,
+ /* inclusive= */ false,
+ /* stayInBounds= */ false))
.isEqualTo(4);
}
@Test
public void
longArrayBinarySearchFloor_targetBetweenValuesAndInclusiveTrue_returnsIndexWhereTargetShouldBeInserted() {
- LongArray longArray = new LongArray();
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(1);
- longArray.add(3);
- longArray.add(5);
-
assertThat(
binarySearchFloor(
- longArray, /* value= */ 2, /* inclusive= */ true, /* stayInBounds= */ false))
+ newLongArray(1, 1, 1, 1, 1, 3, 5),
+ /* value= */ 2,
+ /* inclusive= */ true,
+ /* stayInBounds= */ false))
.isEqualTo(4);
}
@@ -682,13 +652,13 @@
}
@Test
- public void testParseXsDuration() {
+ public void parseXsDuration_returnsParsedDurationInMillis() {
assertThat(parseXsDuration("PT150.279S")).isEqualTo(150279L);
assertThat(parseXsDuration("PT1.500S")).isEqualTo(1500L);
}
@Test
- public void testParseXsDateTime() throws Exception {
+ public void parseXsDateTime_returnsParsedDateTimeInMillis() throws Exception {
assertThat(parseXsDateTime("2014-06-19T23:07:42")).isEqualTo(1403219262000L);
assertThat(parseXsDateTime("2014-08-06T11:00:00Z")).isEqualTo(1407322800000L);
assertThat(parseXsDateTime("2014-08-06T11:00:00,000Z")).isEqualTo(1407322800000L);
@@ -699,7 +669,7 @@
}
@Test
- public void testToUnsignedLongPositiveValue() {
+ public void toUnsignedLong_withPositiveValue_returnsValue() {
int x = 0x05D67F23;
long result = Util.toUnsignedLong(x);
@@ -708,7 +678,7 @@
}
@Test
- public void testToUnsignedLongNegativeValue() {
+ public void toUnsignedLong_withNegativeValue_returnsValue() {
int x = 0xF5D67F23;
long result = Util.toUnsignedLong(x);
@@ -717,50 +687,73 @@
}
@Test
- public void testToLongZeroValue() {
+ public void toLong_withZeroValue_returnsZero() {
assertThat(Util.toLong(0, 0)).isEqualTo(0);
}
@Test
- public void testToLongValue() {
+ public void toLong_withLongValue_returnsValue() {
assertThat(Util.toLong(1, -4)).isEqualTo(0x1FFFFFFFCL);
}
@Test
- public void testToLongBigValue() {
+ public void toLong_withBigValue_returnsValue() {
assertThat(Util.toLong(0x7ABCDEF, 0x12345678)).isEqualTo(0x7ABCDEF_12345678L);
}
@Test
- public void testToLongMaxValue() {
+ public void toLong_withMaxValue_returnsValue() {
assertThat(Util.toLong(0x0FFFFFFF, 0xFFFFFFFF)).isEqualTo(0x0FFFFFFF_FFFFFFFFL);
}
@Test
- public void testToLongBigNegativeValue() {
+ public void toLong_withBigNegativeValue_returnsValue() {
assertThat(Util.toLong(0xFEDCBA, 0x87654321)).isEqualTo(0xFEDCBA_87654321L);
}
@Test
- public void testGetCodecsOfType() {
+ public void toHexString_returnsHexString() {
+ byte[] bytes = TestUtil.createByteArray(0x12, 0xFC, 0x06);
+
+ assertThat(Util.toHexString(bytes)).isEqualTo("12fc06");
+ }
+
+ @Test
+ public void getCodecsOfType_withNull_returnsNull() {
assertThat(getCodecsOfType(null, C.TRACK_TYPE_VIDEO)).isNull();
+ }
+
+ @Test
+ public void getCodecsOfType_withInvalidTrackType_returnsNull() {
assertThat(getCodecsOfType("avc1.64001e,vp9.63.1", C.TRACK_TYPE_AUDIO)).isNull();
+ }
+
+ @Test
+ public void getCodecsOfType_withAudioTrack_returnsCodec() {
assertThat(getCodecsOfType(" vp9.63.1, ec-3 ", C.TRACK_TYPE_AUDIO)).isEqualTo("ec-3");
+ }
+
+ @Test
+ public void getCodecsOfType_withVideoTrack_returnsCodec() {
assertThat(getCodecsOfType("avc1.61e, vp9.63.1, ec-3 ", C.TRACK_TYPE_VIDEO))
.isEqualTo("avc1.61e,vp9.63.1");
assertThat(getCodecsOfType("avc1.61e, vp9.63.1, ec-3 ", C.TRACK_TYPE_VIDEO))
.isEqualTo("avc1.61e,vp9.63.1");
+ }
+
+ @Test
+ public void getCodecsOfType_withInvalidCodec_returnsNull() {
assertThat(getCodecsOfType("invalidCodec1, invalidCodec2 ", C.TRACK_TYPE_AUDIO)).isNull();
}
@Test
- public void testUnescapeInvalidFileName() {
+ public void unescapeFileName_invalidFileName_returnsNull() {
assertThat(Util.unescapeFileName("%a")).isNull();
assertThat(Util.unescapeFileName("%xyz")).isNull();
}
@Test
- public void testEscapeUnescapeFileName() {
+ public void escapeUnescapeFileName_returnsEscapedString() {
assertEscapeUnescapeFileName("just+a regular+fileName", "just+a regular+fileName");
assertEscapeUnescapeFileName("key:value", "key%3avalue");
assertEscapeUnescapeFileName("<>:\"/\\|?*%", "%3c%3e%3a%22%2f%5c%7c%3f%2a%25");
@@ -773,7 +766,7 @@
}
@Test
- public void testCrc32() {
+ public void crc32_returnsUpdatedCrc32() {
byte[] bytes = {0x5F, 0x78, 0x04, 0x7B, 0x5F};
int start = 1;
int end = 4;
@@ -785,7 +778,7 @@
}
@Test
- public void testCrc8() {
+ public void crc8_returnsUpdatedCrc8() {
byte[] bytes = {0x5F, 0x78, 0x04, 0x7B, 0x5F};
int start = 1;
int end = 4;
@@ -797,7 +790,7 @@
}
@Test
- public void testInflate() {
+ public void inflate_withDeflatedData_success() {
byte[] testData = TestUtil.buildTestData(/*arbitrary test data size*/ 256 * 1024);
byte[] compressedData = new byte[testData.length * 2];
Deflater compresser = new Deflater(9);
@@ -813,8 +806,9 @@
assertThat(Arrays.copyOf(output.data, output.limit())).isEqualTo(testData);
}
+ // TODO: Revert to @Config(sdk = Config.ALL_SDKS) once b/143232359 is resolved
@Test
- @Config(sdk = Config.ALL_SDKS)
+ @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.TARGET_SDK)
public void normalizeLanguageCode_keepsUndefinedTagsUnchanged() {
assertThat(Util.normalizeLanguageCode(null)).isNull();
assertThat(Util.normalizeLanguageCode("")).isEmpty();
@@ -822,8 +816,9 @@
assertThat(Util.normalizeLanguageCode("DoesNotExist")).isEqualTo("doesnotexist");
}
+ // TODO: Revert to @Config(sdk = Config.ALL_SDKS) once b/143232359 is resolved
@Test
- @Config(sdk = Config.ALL_SDKS)
+ @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.TARGET_SDK)
public void normalizeLanguageCode_normalizesCodeToTwoLetterISOAndLowerCase_keepingAllSubtags() {
assertThat(Util.normalizeLanguageCode("es")).isEqualTo("es");
assertThat(Util.normalizeLanguageCode("spa")).isEqualTo("es");
@@ -841,8 +836,9 @@
assertThat(Util.normalizeLanguageCode("sv-illegalSubtag")).isEqualTo("sv-illegalsubtag");
}
+ // TODO: Revert to @Config(sdk = Config.ALL_SDKS) once b/143232359 is resolved
@Test
- @Config(sdk = Config.ALL_SDKS)
+ @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.TARGET_SDK)
public void normalizeLanguageCode_iso6392BibliographicalAndTextualCodes_areNormalizedToSameTag() {
// See https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes.
assertThat(Util.normalizeLanguageCode("alb")).isEqualTo(Util.normalizeLanguageCode("sqi"));
@@ -868,8 +864,9 @@
assertThat(Util.normalizeLanguageCode("wel")).isEqualTo(Util.normalizeLanguageCode("cym"));
}
+ // TODO: Revert to @Config(sdk = Config.ALL_SDKS) once b/143232359 is resolved
@Test
- @Config(sdk = Config.ALL_SDKS)
+ @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.TARGET_SDK)
public void
normalizeLanguageCode_deprecatedLanguageTagsAndModernReplacement_areNormalizedToSameTag() {
// See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes, "ISO 639:1988"
@@ -906,8 +903,9 @@
.isEqualTo(Util.normalizeLanguageCode("zh-hsn"));
}
+ // TODO: Revert to @Config(sdk = Config.ALL_SDKS) once b/143232359 is resolved
@Test
- @Config(sdk = Config.ALL_SDKS)
+ @Config(minSdk = Config.OLDEST_SDK, maxSdk = Config.TARGET_SDK)
public void normalizeLanguageCode_macrolanguageTags_areFullyMaintained() {
// See https://en.wikipedia.org/wiki/ISO_639_macrolanguage
assertThat(Util.normalizeLanguageCode("zh-cmn")).isEqualTo("zh-cmn");
@@ -936,6 +934,21 @@
assertThat(Util.normalizeLanguageCode("hsn")).isEqualTo("zh-hsn");
}
+ @Test
+ public void toList() {
+ assertThat(Util.toList(0, 3, 4)).containsExactly(0, 3, 4).inOrder();
+ }
+
+ @Test
+ public void toList_nullPassed_returnsEmptyList() {
+ assertThat(Util.toList(null)).isEmpty();
+ }
+
+ @Test
+ public void toList_emptyArrayPassed_returnsEmptyList() {
+ assertThat(Util.toList(new int[0])).isEmpty();
+ }
+
private static void assertEscapeUnescapeFileName(String fileName, String escapedFileName) {
assertThat(escapeFileName(fileName)).isEqualTo(escapedFileName);
assertThat(unescapeFileName(escapedFileName)).isEqualTo(fileName);
@@ -945,4 +958,12 @@
String escapedFileName = Util.escapeFileName(fileName);
assertThat(unescapeFileName(escapedFileName)).isEqualTo(fileName);
}
+
+ private static LongArray newLongArray(long... values) {
+ LongArray longArray = new LongArray();
+ for (long value : values) {
+ longArray.add(value);
+ }
+ return longArray;
+ }
}
diff --git a/tree/library/core/build.gradle b/tree/library/core/build.gradle
index 0c28a39..8b8c3fd 100644
--- a/tree/library/core/build.gradle
+++ b/tree/library/core/build.gradle
@@ -41,6 +41,11 @@
}
}
+ sourceSets {
+ androidTest.assets.srcDir '../../testdata/src/test/assets/'
+ test.assets.srcDir '../../testdata/src/test/assets/'
+ }
+
testOptions.unitTests.includeAndroidResources = true
}
@@ -48,13 +53,17 @@
api project(modulePrefix + 'library-common')
api project(modulePrefix + 'library-extractor')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
+ compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'com.google.guava:guava:' + guavaVersion
androidTestImplementation 'com.linkedin.dexmaker:dexmaker:' + dexmakerVersion
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion
- androidTestImplementation project(modulePrefix + 'testutils')
+ androidTestImplementation(project(modulePrefix + 'testutils')) {
+ exclude module: modulePrefix.substring(1) + 'library-core'
+ }
testImplementation 'com.google.guava:guava:' + guavaVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils')
diff --git a/tree/library/core/proguard-rules.txt b/tree/library/core/proguard-rules.txt
index 2ffddca..36038b9 100644
--- a/tree/library/core/proguard-rules.txt
+++ b/tree/library/core/proguard-rules.txt
@@ -25,6 +25,10 @@
-keepclassmembers class com.google.android.exoplayer2.ext.av1.Libgav1VideoRenderer {
<init>(long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int);
}
+-dontnote com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer
+-keepclassmembers class com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer {
+ <init>(long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int);
+}
-dontnote com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer
-keepclassmembers class com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer {
<init>(android.os.Handler, com.google.android.exoplayer2.audio.AudioRendererEventListener, com.google.android.exoplayer2.audio.AudioProcessor[]);
@@ -58,7 +62,7 @@
<init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offline.DownloaderConstructorHelper);
}
-# Constructors accessed via reflection in DownloadHelper
+# Constructors accessed via reflection in DefaultMediaSourceFactory and DownloadHelper
-dontnote com.google.android.exoplayer2.source.dash.DashMediaSource$Factory
-keepclasseswithmembers class com.google.android.exoplayer2.source.dash.DashMediaSource$Factory {
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
diff --git a/tree/library/core/src/androidTest/assets/mp4/testvid_1022ms.mp4 b/tree/library/core/src/androidTest/assets/mp4/testvid_1022ms.mp4
deleted file mode 100644
index bbd2729..0000000
--- a/tree/library/core/src/androidTest/assets/mp4/testvid_1022ms.mp4
+++ /dev/null
Binary files differ
diff --git a/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java
new file mode 100644
index 0000000..220f331
--- /dev/null
+++ b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import com.google.android.exoplayer2.testutil.DummyMainThread;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link StreamVolumeManager}. */
+@RunWith(AndroidJUnit4.class)
+public class StreamVolumeManagerTest {
+
+ private static final long TIMEOUT_MS = 1_000;
+
+ private AudioManager audioManager;
+ private TestListener testListener;
+ private DummyMainThread testThread;
+ private StreamVolumeManager streamVolumeManager;
+
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ testListener = new TestListener();
+
+ testThread = new DummyMainThread();
+ testThread.runOnMainThread(
+ () ->
+ streamVolumeManager =
+ new StreamVolumeManager(context, new Handler(Looper.myLooper()), testListener));
+ }
+
+ @After
+ public void tearDown() {
+ testThread.runOnMainThread(() -> streamVolumeManager.release());
+ testThread.release();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void getMinVolume_returnsStreamMinVolume() {
+ testThread.runOnMainThread(
+ () -> {
+ int streamMinVolume = audioManager.getStreamMinVolume(C.STREAM_TYPE_DEFAULT);
+ assertThat(streamVolumeManager.getMinVolume()).isEqualTo(streamMinVolume);
+ });
+ }
+
+ @Test
+ public void getMaxVolume_returnsStreamMaxVolume() {
+ testThread.runOnMainThread(
+ () -> {
+ int streamMaxVolume = audioManager.getStreamMaxVolume(C.STREAM_TYPE_DEFAULT);
+ assertThat(streamVolumeManager.getMaxVolume()).isEqualTo(streamMaxVolume);
+ });
+ }
+
+ @Test
+ public void getVolume_returnsStreamVolume() {
+ testThread.runOnMainThread(
+ () -> {
+ int streamVolume = audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT);
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(streamVolume);
+ });
+ }
+
+ @Test
+ public void setVolume_changesStreamVolume() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume) {
+ return;
+ }
+
+ int oldVolume = streamVolumeManager.getVolume();
+ int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;
+
+ streamVolumeManager.setVolume(targetVolume);
+
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
+ assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
+ assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
+ });
+ }
+
+ @Test
+ public void setVolume_withOutOfRange_isIgnored() {
+ testThread.runOnMainThread(
+ () -> {
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ int minVolume = streamVolumeManager.getMinVolume();
+ int oldVolume = streamVolumeManager.getVolume();
+
+ streamVolumeManager.setVolume(maxVolume + 1);
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);
+
+ streamVolumeManager.setVolume(minVolume - 1);
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(oldVolume);
+ });
+ }
+
+ @Test
+ public void increaseVolume_increasesStreamVolumeByOne() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume) {
+ return;
+ }
+
+ streamVolumeManager.setVolume(minVolume);
+ int targetVolume = minVolume + 1;
+
+ streamVolumeManager.increaseVolume();
+
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
+ assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
+ assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
+ });
+ }
+
+ @Test
+ public void increaseVolume_onMaxVolume_isIgnored() {
+ testThread.runOnMainThread(
+ () -> {
+ int maxVolume = streamVolumeManager.getMaxVolume();
+
+ streamVolumeManager.setVolume(maxVolume);
+ streamVolumeManager.increaseVolume();
+
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(maxVolume);
+ });
+ }
+
+ @Test
+ public void decreaseVolume_decreasesStreamVolumeByOne() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume) {
+ return;
+ }
+
+ streamVolumeManager.setVolume(maxVolume);
+ int targetVolume = maxVolume - 1;
+
+ streamVolumeManager.decreaseVolume();
+
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(targetVolume);
+ assertThat(testListener.lastStreamVolume).isEqualTo(targetVolume);
+ assertThat(audioManager.getStreamVolume(C.STREAM_TYPE_DEFAULT)).isEqualTo(targetVolume);
+ });
+ }
+
+ @Test
+ public void decreaseVolume_onMinVolume_isIgnored() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+
+ streamVolumeManager.setVolume(minVolume);
+ streamVolumeManager.decreaseVolume();
+
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(minVolume);
+ });
+ }
+
+ @Test
+ public void setStreamType_notifiesStreamTypeAndVolume() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume) {
+ return;
+ }
+
+ int testStreamType = C.STREAM_TYPE_ALARM;
+ int testStreamVolume = audioManager.getStreamVolume(testStreamType);
+
+ int oldVolume = streamVolumeManager.getVolume();
+ if (oldVolume == testStreamVolume) {
+ int differentVolume = oldVolume == minVolume ? maxVolume : minVolume;
+ streamVolumeManager.setVolume(differentVolume);
+ }
+
+ streamVolumeManager.setStreamType(testStreamType);
+
+ assertThat(testListener.lastStreamType).isEqualTo(testStreamType);
+ assertThat(testListener.lastStreamVolume).isEqualTo(testStreamVolume);
+ assertThat(streamVolumeManager.getVolume()).isEqualTo(testStreamVolume);
+ });
+ }
+
+ @Test
+ public void onStreamVolumeChanged_isCalled_whenAudioManagerChangesIt() throws Exception {
+ AtomicInteger targetVolumeRef = new AtomicInteger();
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume) {
+ return;
+ }
+
+ int oldVolume = streamVolumeManager.getVolume();
+ int targetVolume = oldVolume == maxVolume ? minVolume : maxVolume;
+ targetVolumeRef.set(targetVolume);
+
+ audioManager.setStreamVolume(C.STREAM_TYPE_DEFAULT, targetVolume, /* flags= */ 0);
+ });
+
+ testListener.onStreamVolumeChangedLatch.await(TIMEOUT_MS, MILLISECONDS);
+ assertThat(testListener.lastStreamVolume).isEqualTo(targetVolumeRef.get());
+ }
+
+ private static class TestListener implements StreamVolumeManager.Listener {
+
+ @C.StreamType private int lastStreamType;
+ private int lastStreamVolume;
+ public final CountDownLatch onStreamVolumeChangedLatch;
+
+ public TestListener() {
+ onStreamVolumeChangedLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onStreamTypeChanged(@C.StreamType int streamType) {
+ lastStreamType = streamType;
+ }
+
+ @Override
+ public void onStreamVolumeChanged(int streamVolume) {
+ lastStreamVolume = streamVolume;
+ onStreamVolumeChangedLatch.countDown();
+ }
+ }
+}
diff --git a/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java
index 2021cb2..39c12e1 100644
--- a/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java
+++ b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java
@@ -43,40 +43,40 @@
public final class ContentDataSourceTest {
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
- private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
+ private static final String DATA_PATH = "mp3/1024_incrementing_bytes.mp3";
@Test
- public void testRead() throws Exception {
+ public void read() throws Exception {
assertData(0, C.LENGTH_UNSET, false);
}
@Test
- public void testReadPipeMode() throws Exception {
+ public void readPipeMode() throws Exception {
assertData(0, C.LENGTH_UNSET, true);
}
@Test
- public void testReadFixedLength() throws Exception {
+ public void readFixedLength() throws Exception {
assertData(0, 100, false);
}
@Test
- public void testReadFromOffsetToEndOfInput() throws Exception {
+ public void readFromOffsetToEndOfInput() throws Exception {
assertData(1, C.LENGTH_UNSET, false);
}
@Test
- public void testReadFromOffsetToEndOfInputPipeMode() throws Exception {
+ public void readFromOffsetToEndOfInputPipeMode() throws Exception {
assertData(1, C.LENGTH_UNSET, true);
}
@Test
- public void testReadFromOffsetFixedLength() throws Exception {
+ public void readFromOffsetFixedLength() throws Exception {
assertData(1, 100, false);
}
@Test
- public void testReadInvalidUri() throws Exception {
+ public void readInvalidUri() throws Exception {
ContentDataSource dataSource =
new ContentDataSource(InstrumentationRegistry.getTargetContext());
Uri contentUri = TestContentProvider.buildUri("does/not.exist", false);
@@ -97,7 +97,7 @@
ContentDataSource dataSource =
new ContentDataSource(InstrumentationRegistry.getTargetContext());
try {
- DataSpec dataSpec = new DataSpec(contentUri, offset, length, null);
+ DataSpec dataSpec = new DataSpec(contentUri, offset, length);
byte[] completeData =
TestUtil.getByteArray(InstrumentationRegistry.getTargetContext(), DATA_PATH);
byte[] expectedData = Arrays.copyOfRange(completeData, offset,
diff --git a/tree/library/core/src/main/AndroidManifest.xml b/tree/library/core/src/main/AndroidManifest.xml
index b17180f..1a6971f 100644
--- a/tree/library/core/src/main/AndroidManifest.xml
+++ b/tree/library/core/src/main/AndroidManifest.xml
@@ -14,4 +14,7 @@
limitations under the License.
-->
-<manifest package="com.google.android.exoplayer2.core" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.exoplayer2.core">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+</manifest>
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java
index c9aa9e5..5aeca44 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java
@@ -24,7 +24,6 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.audio.AudioAttributes;
-import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
@@ -76,15 +75,12 @@
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
- AUDIO_FOCUS_STATE_LOST_FOCUS,
AUDIO_FOCUS_STATE_NO_FOCUS,
AUDIO_FOCUS_STATE_HAVE_FOCUS,
AUDIO_FOCUS_STATE_LOSS_TRANSIENT,
AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK
})
private @interface AudioFocusState {}
- /** No audio focus was held, but has been lost by another app taking it permanently. */
- private static final int AUDIO_FOCUS_STATE_LOST_FOCUS = -1;
/** No audio focus is currently being held. */
private static final int AUDIO_FOCUS_STATE_NO_FOCUS = 0;
/** The requested audio focus is currently held. */
@@ -101,7 +97,7 @@
private final AudioManager audioManager;
private final AudioFocusListener focusListener;
- private final PlayerControl playerControl;
+ @Nullable private PlayerControl playerControl;
@Nullable private AudioAttributes audioAttributes;
@AudioFocusState private int audioFocusState;
@@ -134,64 +130,45 @@
/**
* Sets audio attributes that should be used to manage audio focus.
*
+ * <p>Call {@link #updateAudioFocus(boolean, int)} to update the audio focus based on these
+ * attributes.
+ *
* @param audioAttributes The audio attributes or {@code null} if audio focus should not be
* managed automatically.
- * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}.
- * @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}.
- * @return A {@link PlayerCommand} to execute on the player.
*/
- @PlayerCommand
- public int setAudioAttributes(
- @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) {
+ public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) {
if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
this.audioAttributes = audioAttributes;
focusGain = convertAudioAttributesToFocusGain(audioAttributes);
-
Assertions.checkArgument(
focusGain == C.AUDIOFOCUS_GAIN || focusGain == C.AUDIOFOCUS_NONE,
"Automatic handling of audio focus is only available for USAGE_MEDIA and USAGE_GAME.");
- if (playWhenReady
- && (playerState == Player.STATE_BUFFERING || playerState == Player.STATE_READY)) {
- return requestAudioFocus();
- }
}
-
- return playerState == Player.STATE_IDLE
- ? handleIdle(playWhenReady)
- : handlePrepare(playWhenReady);
}
/**
- * Called by a player as part of {@link ExoPlayer#prepare(MediaSource, boolean, boolean)}.
+ * Called by the player to abandon or request audio focus based on the desired player state.
*
- * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}.
+ * @param playWhenReady The desired value of playWhenReady.
+ * @param playbackState The desired playback state.
* @return A {@link PlayerCommand} to execute on the player.
*/
@PlayerCommand
- public int handlePrepare(boolean playWhenReady) {
+ public int updateAudioFocus(boolean playWhenReady, @Player.State int playbackState) {
+ if (shouldAbandonAudioFocus(playbackState)) {
+ abandonAudioFocus();
+ return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
+ }
return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY;
}
/**
- * Called by the player as part of {@link ExoPlayer#setPlayWhenReady(boolean)}.
- *
- * @param playWhenReady The desired value of playWhenReady.
- * @param playerState The current state of the player.
- * @return A {@link PlayerCommand} to execute on the player.
+ * Called when the manager is no longer required. Audio focus will be released without making any
+ * calls to the {@link PlayerControl}.
*/
- @PlayerCommand
- public int handleSetPlayWhenReady(boolean playWhenReady, int playerState) {
- if (!playWhenReady) {
- abandonAudioFocus();
- return PLAYER_COMMAND_DO_NOT_PLAY;
- }
-
- return playerState == Player.STATE_IDLE ? handleIdle(playWhenReady) : requestAudioFocus();
- }
-
- /** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */
- public void handleStop() {
- abandonAudioFocus(/* forceAbandon= */ true);
+ public void release() {
+ playerControl = null;
+ abandonAudioFocus();
}
// Internal methods.
@@ -201,62 +178,35 @@
return focusListener;
}
- @PlayerCommand
- private int handleIdle(boolean playWhenReady) {
- return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
+ private boolean shouldAbandonAudioFocus(@Player.State int playbackState) {
+ return playbackState == Player.STATE_IDLE || focusGain != C.AUDIOFOCUS_GAIN;
}
@PlayerCommand
private int requestAudioFocus() {
- int focusRequestResult;
-
- if (focusGain == C.AUDIOFOCUS_NONE) {
- if (audioFocusState != AUDIO_FOCUS_STATE_NO_FOCUS) {
- abandonAudioFocus(/* forceAbandon= */ true);
- }
+ if (audioFocusState == AUDIO_FOCUS_STATE_HAVE_FOCUS) {
return PLAYER_COMMAND_PLAY_WHEN_READY;
}
-
- if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
- if (Util.SDK_INT >= 26) {
- focusRequestResult = requestAudioFocusV26();
- } else {
- focusRequestResult = requestAudioFocusDefault();
- }
- audioFocusState =
- focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
- ? AUDIO_FOCUS_STATE_HAVE_FOCUS
- : AUDIO_FOCUS_STATE_NO_FOCUS;
- }
-
- if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
+ int requestResult = Util.SDK_INT >= 26 ? requestAudioFocusV26() : requestAudioFocusDefault();
+ if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
+ return PLAYER_COMMAND_PLAY_WHEN_READY;
+ } else {
+ setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
return PLAYER_COMMAND_DO_NOT_PLAY;
}
-
- return audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT
- ? PLAYER_COMMAND_WAIT_FOR_CALLBACK
- : PLAYER_COMMAND_PLAY_WHEN_READY;
}
private void abandonAudioFocus() {
- abandonAudioFocus(/* forceAbandon= */ false);
- }
-
- private void abandonAudioFocus(boolean forceAbandon) {
- if (focusGain == C.AUDIOFOCUS_NONE && audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
+ if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
return;
}
-
- if (focusGain != C.AUDIOFOCUS_GAIN
- || audioFocusState == AUDIO_FOCUS_STATE_LOST_FOCUS
- || forceAbandon) {
- if (Util.SDK_INT >= 26) {
- abandonAudioFocusV26();
- } else {
- abandonAudioFocusDefault();
- }
- audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS;
+ if (Util.SDK_INT >= 26) {
+ abandonAudioFocusV26();
+ } else {
+ abandonAudioFocusDefault();
}
+ setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
}
private int requestAudioFocusDefault() {
@@ -312,7 +262,6 @@
*/
@C.AudioFocusGain
private static int convertAudioAttributesToFocusGain(@Nullable AudioAttributes audioAttributes) {
-
if (audioAttributes == null) {
// Don't handle audio focus. It may be either video only contents or developers
// want to have more finer grained control. (e.g. adding audio focus listener)
@@ -382,63 +331,55 @@
}
}
- private void handleAudioFocusChange(int focusChange) {
- // Convert the platform focus change to internal state.
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- audioFocusState = AUDIO_FOCUS_STATE_LOST_FOCUS;
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- if (willPauseWhenDucked()) {
- audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT;
- } else {
- audioFocusState = AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK;
- }
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- audioFocusState = AUDIO_FOCUS_STATE_HAVE_FOCUS;
- break;
- default:
- Log.w(TAG, "Unknown focus change type: " + focusChange);
- // Early return.
- return;
+ private void setAudioFocusState(@AudioFocusState int audioFocusState) {
+ if (this.audioFocusState == audioFocusState) {
+ return;
}
-
- // Handle the internal state (change).
- switch (audioFocusState) {
- case AUDIO_FOCUS_STATE_NO_FOCUS:
- // Focus was not requested; nothing to do.
- break;
- case AUDIO_FOCUS_STATE_LOST_FOCUS:
- playerControl.executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
- abandonAudioFocus(/* forceAbandon= */ true);
- break;
- case AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
- playerControl.executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
- break;
- case AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK:
- // Volume will be adjusted by the code below.
- break;
- case AUDIO_FOCUS_STATE_HAVE_FOCUS:
- playerControl.executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
- break;
- default:
- throw new IllegalStateException("Unknown audio focus state: " + audioFocusState);
- }
+ this.audioFocusState = audioFocusState;
float volumeMultiplier =
(audioFocusState == AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK)
? AudioFocusManager.VOLUME_MULTIPLIER_DUCK
: AudioFocusManager.VOLUME_MULTIPLIER_DEFAULT;
- if (AudioFocusManager.this.volumeMultiplier != volumeMultiplier) {
- AudioFocusManager.this.volumeMultiplier = volumeMultiplier;
+ if (this.volumeMultiplier == volumeMultiplier) {
+ return;
+ }
+ this.volumeMultiplier = volumeMultiplier;
+ if (playerControl != null) {
playerControl.setVolumeMultiplier(volumeMultiplier);
}
}
+ private void handlePlatformAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ setAudioFocusState(AUDIO_FOCUS_STATE_HAVE_FOCUS);
+ executePlayerCommand(PLAYER_COMMAND_PLAY_WHEN_READY);
+ return;
+ case AudioManager.AUDIOFOCUS_LOSS:
+ executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY);
+ abandonAudioFocus();
+ return;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || willPauseWhenDucked()) {
+ executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK);
+ setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT);
+ } else {
+ setAudioFocusState(AUDIO_FOCUS_STATE_LOSS_TRANSIENT_DUCK);
+ }
+ return;
+ default:
+ Log.w(TAG, "Unknown focus change type: " + focusChange);
+ }
+ }
+
+ private void executePlayerCommand(@PlayerCommand int playerCommand) {
+ if (playerControl != null) {
+ playerControl.executePlayerCommand(playerCommand);
+ }
+ }
+
// Internal audio focus listener.
private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
@@ -450,7 +391,7 @@
@Override
public void onAudioFocusChange(int focusChange) {
- eventHandler.post(() -> handleAudioFocusChange(focusChange));
+ eventHandler.post(() -> handlePlatformAudioFocusChange(focusChange));
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java
index 0f00621..5692b1d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java
@@ -17,6 +17,8 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Util;
+import java.util.Collections;
+import java.util.List;
/** Abstract base {@link Player} which implements common implementation independent methods. */
public abstract class BasePlayer implements Player {
@@ -28,6 +30,54 @@
}
@Override
+ public void setMediaItem(MediaItem mediaItem) {
+ setMediaItems(Collections.singletonList(mediaItem));
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
+ setMediaItems(Collections.singletonList(mediaItem), /* startWindowIndex= */ 0, startPositionMs);
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
+ setMediaItems(Collections.singletonList(mediaItem), resetPosition);
+ }
+
+ @Override
+ public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
+ setMediaItems(
+ mediaItems, /* startWindowIndex= */ C.INDEX_UNSET, /* startPositionMs= */ C.TIME_UNSET);
+ }
+
+ @Override
+ public void setMediaItems(List<MediaItem> mediaItems) {
+ setMediaItems(mediaItems, /* resetPosition= */ true);
+ }
+
+ @Override
+ public void addMediaItem(int index, MediaItem mediaItem) {
+ addMediaItems(index, Collections.singletonList(mediaItem));
+ }
+
+ @Override
+ public void addMediaItem(MediaItem mediaItem) {
+ addMediaItems(Collections.singletonList(mediaItem));
+ }
+
+ @Override
+ public void moveMediaItem(int currentIndex, int newIndex) {
+ if (currentIndex != newIndex) {
+ moveMediaItems(/* fromIndex= */ currentIndex, /* toIndex= */ currentIndex + 1, newIndex);
+ }
+ }
+
+ @Override
+ public void removeMediaItem(int index) {
+ removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
+ }
+
+ @Override
public final void play() {
setPlayWhenReady(true);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java
index 10573af..fc2cbbc 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java
@@ -15,17 +15,11 @@
*/
package com.google.android.exoplayer2;
-import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.drm.DrmInitData;
-import com.google.android.exoplayer2.drm.DrmSession;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock;
-import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
@@ -83,13 +77,19 @@
}
@Override
- public final void enable(RendererConfiguration configuration, Format[] formats,
- SampleStream stream, long positionUs, boolean joining, long offsetUs)
+ public final void enable(
+ RendererConfiguration configuration,
+ Format[] formats,
+ SampleStream stream,
+ long positionUs,
+ boolean joining,
+ boolean mayRenderStartOfStream,
+ long offsetUs)
throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration;
state = STATE_ENABLED;
- onEnabled(joining);
+ onEnabled(joining, mayRenderStartOfStream);
replaceStream(formats, stream, offsetUs);
onPositionReset(positionUs, joining);
}
@@ -194,27 +194,30 @@
/**
* Called when the renderer is enabled.
- * <p>
- * The default implementation is a no-op.
+ *
+ * <p>The default implementation is a no-op.
*
* @param joining Whether this renderer is being enabled to join an ongoing playback.
+ * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
+ * stream even if the state is not {@link #STATE_STARTED} yet.
* @throws ExoPlaybackException If an error occurs.
*/
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
// Do nothing.
}
/**
* Called when the renderer's stream has changed. This occurs when the renderer is enabled after
- * {@link #onEnabled(boolean)} has been called, and also when the stream has been replaced whilst
- * the renderer is enabled or started.
- * <p>
- * The default implementation is a no-op.
+ * {@link #onEnabled(boolean, boolean)} has been called, and also when the stream has been
+ * replaced whilst the renderer is enabled or started.
+ *
+ * <p>The default implementation is a no-op.
*
* @param formats The enabled formats.
- * @param offsetUs The offset that will be added to the timestamps of buffers read via
- * {@link #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input
- * buffers have monotonically increasing timestamps.
+ * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link
+ * #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input buffers have
+ * monotonically increasing timestamps.
* @throws ExoPlaybackException If an error occurs.
*/
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
@@ -299,35 +302,6 @@
return configuration;
}
- /** Returns a {@link DrmSession} ready for assignment, handling resource management. */
- @Nullable
- protected final <T extends ExoMediaCrypto> DrmSession<T> getUpdatedSourceDrmSession(
- @Nullable Format oldFormat,
- Format newFormat,
- @Nullable DrmSessionManager<T> drmSessionManager,
- @Nullable DrmSession<T> existingSourceSession)
- throws ExoPlaybackException {
- boolean drmInitDataChanged =
- !Util.areEqual(newFormat.drmInitData, oldFormat == null ? null : oldFormat.drmInitData);
- if (!drmInitDataChanged) {
- return existingSourceSession;
- }
- @Nullable DrmSession<T> newSourceDrmSession = null;
- if (newFormat.drmInitData != null) {
- if (drmSessionManager == null) {
- throw createRendererException(
- new IllegalStateException("Media requires a DrmSessionManager"), newFormat);
- }
- newSourceDrmSession =
- drmSessionManager.acquireSession(
- Assertions.checkNotNull(Looper.myLooper()), newFormat.drmInitData);
- }
- if (existingSourceSession != null) {
- existingSourceSession.release();
- }
- return newSourceDrmSession;
- }
-
/**
* Returns the index of the renderer within the player.
*/
@@ -356,7 +330,8 @@
throwRendererExceptionIsExecuting = false;
}
}
- return ExoPlaybackException.createForRenderer(cause, getIndex(), format, formatSupport);
+ return ExoPlaybackException.createForRenderer(
+ cause, getName(), getIndex(), format, formatSupport);
}
/**
@@ -371,12 +346,12 @@
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
- * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
- * {@link C#RESULT_BUFFER_READ}.
+ * @return The status of read, one of {@link SampleStream.ReadDataResult}.
*/
+ @SampleStream.ReadDataResult
protected final int readSource(
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
- int result = stream.readData(formatHolder, buffer, formatRequired);
+ @SampleStream.ReadDataResult int result = stream.readData(formatHolder, buffer, formatRequired);
if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) {
readingPositionUs = C.TIME_END_OF_SOURCE;
@@ -387,7 +362,11 @@
} else if (result == C.RESULT_FORMAT_READ) {
Format format = formatHolder.format;
if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
- format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs);
+ format =
+ format
+ .buildUpon()
+ .setSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs)
+ .build();
formatHolder.format = format;
}
}
@@ -411,26 +390,4 @@
protected final boolean isSourceReady() {
return hasReadStreamToEnd() ? streamIsFinal : stream.isReady();
}
-
- /**
- * Returns whether {@code drmSessionManager} supports the specified {@code drmInitData}, or true
- * if {@code drmInitData} is null.
- *
- * @param drmSessionManager The drm session manager.
- * @param drmInitData {@link DrmInitData} of the format to check for support.
- * @return Whether {@code drmSessionManager} supports the specified {@code drmInitData}, or
- * true if {@code drmInitData} is null.
- */
- protected static boolean supportsFormatDrm(@Nullable DrmSessionManager<?> drmSessionManager,
- @Nullable DrmInitData drmInitData) {
- if (drmInitData == null) {
- // Content is unencrypted.
- return true;
- } else if (drmSessionManager == null) {
- // Content is encrypted, but no drm session manager is available.
- return false;
- }
- return drmSessionManager.canAcquireSession(drmInitData);
- }
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java
index f8749fc..7b78147 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java
@@ -47,6 +47,38 @@
boolean dispatchSeekTo(Player player, int windowIndex, long positionMs);
/**
+ * Dispatches a {@link Player#previous()} operation.
+ *
+ * @param player The {@link Player} to which the operation should be dispatched.
+ * @return True if the operation was dispatched. False if suppressed.
+ */
+ boolean dispatchPrevious(Player player);
+
+ /**
+ * Dispatches a {@link Player#next()} operation.
+ *
+ * @param player The {@link Player} to which the operation should be dispatched.
+ * @return True if the operation was dispatched. False if suppressed.
+ */
+ boolean dispatchNext(Player player);
+
+ /**
+ * Dispatches a rewind operation.
+ *
+ * @param player The {@link Player} to which the operation should be dispatched.
+ * @return True if the operation was dispatched. False if suppressed.
+ */
+ boolean dispatchRewind(Player player);
+
+ /**
+ * Dispatches a fast forward operation.
+ *
+ * @param player The {@link Player} to which the operation should be dispatched.
+ * @return True if the operation was dispatched. False if suppressed.
+ */
+ boolean dispatchFastForward(Player player);
+
+ /**
* Dispatches a {@link Player#setRepeatMode(int)} operation.
*
* @param player The {@link Player} to which the operation should be dispatched.
@@ -72,4 +104,10 @@
* @return True if the operation was dispatched. False if suppressed.
*/
boolean dispatchStop(Player player, boolean reset);
+
+ /** Returns {@code true} if rewind is enabled, {@code false} otherwise. */
+ boolean isRewindEnabled();
+
+ /** Returns {@code true} if fast forward is enabled, {@code false} otherwise. */
+ boolean isFastForwardEnabled();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java
index df3ef36..7f24e61 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java
@@ -15,14 +15,40 @@
*/
package com.google.android.exoplayer2;
-import com.google.android.exoplayer2.Player.RepeatMode;
-
-/**
- * Default {@link ControlDispatcher} that dispatches all operations to the player without
- * modification.
- */
+/** Default {@link ControlDispatcher}. */
public class DefaultControlDispatcher implements ControlDispatcher {
+ /** The default fast forward increment, in milliseconds. */
+ public static final int DEFAULT_FAST_FORWARD_MS = 15000;
+ /** The default rewind increment, in milliseconds. */
+ public static final int DEFAULT_REWIND_MS = 5000;
+
+ private static final int MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
+
+ private final Timeline.Window window;
+
+ private long rewindIncrementMs;
+ private long fastForwardIncrementMs;
+
+ /** Creates an instance. */
+ public DefaultControlDispatcher() {
+ this(DEFAULT_FAST_FORWARD_MS, DEFAULT_REWIND_MS);
+ }
+
+ /**
+ * Creates an instance with the given increments.
+ *
+ * @param fastForwardIncrementMs The fast forward increment in milliseconds. A non-positive value
+ * disables the fast forward operation.
+ * @param rewindIncrementMs The rewind increment in milliseconds. A non-positive value disables
+ * the rewind operation.
+ */
+ public DefaultControlDispatcher(long fastForwardIncrementMs, long rewindIncrementMs) {
+ this.fastForwardIncrementMs = fastForwardIncrementMs;
+ this.rewindIncrementMs = rewindIncrementMs;
+ window = new Timeline.Window();
+ }
+
@Override
public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) {
player.setPlayWhenReady(playWhenReady);
@@ -36,7 +62,58 @@
}
@Override
- public boolean dispatchSetRepeatMode(Player player, @RepeatMode int repeatMode) {
+ public boolean dispatchPrevious(Player player) {
+ Timeline timeline = player.getCurrentTimeline();
+ if (timeline.isEmpty() || player.isPlayingAd()) {
+ return true;
+ }
+ int windowIndex = player.getCurrentWindowIndex();
+ timeline.getWindow(windowIndex, window);
+ int previousWindowIndex = player.getPreviousWindowIndex();
+ if (previousWindowIndex != C.INDEX_UNSET
+ && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
+ || (window.isDynamic && !window.isSeekable))) {
+ player.seekTo(previousWindowIndex, C.TIME_UNSET);
+ } else {
+ player.seekTo(windowIndex, /* positionMs= */ 0);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchNext(Player player) {
+ Timeline timeline = player.getCurrentTimeline();
+ if (timeline.isEmpty() || player.isPlayingAd()) {
+ return true;
+ }
+ int windowIndex = player.getCurrentWindowIndex();
+ int nextWindowIndex = player.getNextWindowIndex();
+ if (nextWindowIndex != C.INDEX_UNSET) {
+ player.seekTo(nextWindowIndex, C.TIME_UNSET);
+ } else if (timeline.getWindow(windowIndex, window).isLive) {
+ player.seekTo(windowIndex, C.TIME_UNSET);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchRewind(Player player) {
+ if (isRewindEnabled() && player.isCurrentWindowSeekable()) {
+ seekToOffset(player, -rewindIncrementMs);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchFastForward(Player player) {
+ if (isFastForwardEnabled() && player.isCurrentWindowSeekable()) {
+ seekToOffset(player, fastForwardIncrementMs);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchSetRepeatMode(Player player, @Player.RepeatMode int repeatMode) {
player.setRepeatMode(repeatMode);
return true;
}
@@ -52,4 +129,54 @@
player.stop(reset);
return true;
}
+
+ @Override
+ public boolean isRewindEnabled() {
+ return rewindIncrementMs > 0;
+ }
+
+ @Override
+ public boolean isFastForwardEnabled() {
+ return fastForwardIncrementMs > 0;
+ }
+
+ /** Returns the rewind increment in milliseconds. */
+ public long getRewindIncrementMs() {
+ return rewindIncrementMs;
+ }
+
+ /** Returns the fast forward increment in milliseconds. */
+ public long getFastForwardIncrementMs() {
+ return fastForwardIncrementMs;
+ }
+
+ /**
+ * @deprecated Create a new instance instead and pass the new instance to the UI component. This
+ * makes sure the UI gets updated and is in sync with the new values.
+ */
+ @Deprecated
+ public void setRewindIncrementMs(long rewindMs) {
+ this.rewindIncrementMs = rewindMs;
+ }
+
+ /**
+ * @deprecated Create a new instance instead and pass the new instance to the UI component. This
+ * makes sure the UI gets updated and is in sync with the new values.
+ */
+ @Deprecated
+ public void setFastForwardIncrementMs(long fastForwardMs) {
+ this.fastForwardIncrementMs = fastForwardMs;
+ }
+
+ // Internal methods.
+
+ private static void seekToOffset(Player player, long offsetMs) {
+ long positionMs = player.getCurrentPosition() + offsetMs;
+ long durationMs = player.getDuration();
+ if (durationMs != C.TIME_UNSET) {
+ positionMs = Math.min(positionMs, durationMs);
+ }
+ positionMs = Math.max(positionMs, 0);
+ player.seekTo(player.getCurrentWindowIndex(), positionMs);
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java
index 7bfd4c7..ce35c89 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java
@@ -20,6 +20,7 @@
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
/**
@@ -29,14 +30,12 @@
/**
* The default minimum duration of media that the player will attempt to ensure is buffered at all
- * times, in milliseconds. This value is only applied to playbacks without video.
+ * times, in milliseconds.
*/
- public static final int DEFAULT_MIN_BUFFER_MS = 15000;
+ public static final int DEFAULT_MIN_BUFFER_MS = 50000;
/**
* The default maximum duration of media that the player will attempt to buffer, in milliseconds.
- * For playbacks with video, this is also the default minimum duration of media that the player
- * will attempt to ensure is buffered.
*/
public static final int DEFAULT_MAX_BUFFER_MS = 50000;
@@ -59,7 +58,7 @@
public static final int DEFAULT_TARGET_BUFFER_BYTES = C.LENGTH_UNSET;
/** The default prioritization of buffer time constraints over size constraints. */
- public static final boolean DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS = true;
+ public static final boolean DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS = false;
/** The default back buffer duration in milliseconds. */
public static final int DEFAULT_BACK_BUFFER_DURATION_MS = 0;
@@ -68,10 +67,10 @@
public static final boolean DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME = false;
/** A default size in bytes for a video buffer. */
- public static final int DEFAULT_VIDEO_BUFFER_SIZE = 500 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
+ public static final int DEFAULT_VIDEO_BUFFER_SIZE = 2000 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
/** A default size in bytes for an audio buffer. */
- public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
+ public static final int DEFAULT_AUDIO_BUFFER_SIZE = 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
/** A default size in bytes for a text buffer. */
public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
@@ -86,12 +85,17 @@
public static final int DEFAULT_MUXED_BUFFER_SIZE =
DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE;
+ /**
+ * The buffer size in bytes that will be used as a minimum target buffer in all cases. This is
+ * also the default target buffer before tracks are selected.
+ */
+ public static final int DEFAULT_MIN_BUFFER_SIZE = 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE;
+
/** Builder for {@link DefaultLoadControl}. */
public static final class Builder {
private DefaultAllocator allocator;
- private int minBufferAudioMs;
- private int minBufferVideoMs;
+ private int minBufferMs;
private int maxBufferMs;
private int bufferForPlaybackMs;
private int bufferForPlaybackAfterRebufferMs;
@@ -103,8 +107,7 @@
/** Constructs a new instance. */
public Builder() {
- minBufferAudioMs = DEFAULT_MIN_BUFFER_MS;
- minBufferVideoMs = DEFAULT_MAX_BUFFER_MS;
+ minBufferMs = DEFAULT_MIN_BUFFER_MS;
maxBufferMs = DEFAULT_MAX_BUFFER_MS;
bufferForPlaybackMs = DEFAULT_BUFFER_FOR_PLAYBACK_MS;
bufferForPlaybackAfterRebufferMs = DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
@@ -158,8 +161,7 @@
"minBufferMs",
"bufferForPlaybackAfterRebufferMs");
assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs");
- this.minBufferAudioMs = minBufferMs;
- this.minBufferVideoMs = minBufferMs;
+ this.minBufferMs = minBufferMs;
this.maxBufferMs = maxBufferMs;
this.bufferForPlaybackMs = bufferForPlaybackMs;
this.bufferForPlaybackAfterRebufferMs = bufferForPlaybackAfterRebufferMs;
@@ -222,8 +224,7 @@
}
return new DefaultLoadControl(
allocator,
- minBufferAudioMs,
- minBufferVideoMs,
+ minBufferMs,
maxBufferMs,
bufferForPlaybackMs,
bufferForPlaybackAfterRebufferMs,
@@ -236,8 +237,7 @@
private final DefaultAllocator allocator;
- private final long minBufferAudioUs;
- private final long minBufferVideoUs;
+ private final long minBufferUs;
private final long maxBufferUs;
private final long bufferForPlaybackUs;
private final long bufferForPlaybackAfterRebufferUs;
@@ -248,7 +248,6 @@
private int targetBufferBytes;
private boolean isBuffering;
- private boolean hasVideo;
/** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */
@SuppressWarnings("deprecation")
@@ -261,8 +260,7 @@
public DefaultLoadControl(DefaultAllocator allocator) {
this(
allocator,
- /* minBufferAudioMs= */ DEFAULT_MIN_BUFFER_MS,
- /* minBufferVideoMs= */ DEFAULT_MAX_BUFFER_MS,
+ DEFAULT_MIN_BUFFER_MS,
DEFAULT_MAX_BUFFER_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS,
@@ -284,8 +282,7 @@
boolean prioritizeTimeOverSizeThresholds) {
this(
allocator,
- /* minBufferAudioMs= */ minBufferMs,
- /* minBufferVideoMs= */ minBufferMs,
+ minBufferMs,
maxBufferMs,
bufferForPlaybackMs,
bufferForPlaybackAfterRebufferMs,
@@ -297,8 +294,7 @@
protected DefaultLoadControl(
DefaultAllocator allocator,
- int minBufferAudioMs,
- int minBufferVideoMs,
+ int minBufferMs,
int maxBufferMs,
int bufferForPlaybackMs,
int bufferForPlaybackAfterRebufferMs,
@@ -309,27 +305,17 @@
assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0");
assertGreaterOrEqual(
bufferForPlaybackAfterRebufferMs, 0, "bufferForPlaybackAfterRebufferMs", "0");
+ assertGreaterOrEqual(minBufferMs, bufferForPlaybackMs, "minBufferMs", "bufferForPlaybackMs");
assertGreaterOrEqual(
- minBufferAudioMs, bufferForPlaybackMs, "minBufferAudioMs", "bufferForPlaybackMs");
- assertGreaterOrEqual(
- minBufferVideoMs, bufferForPlaybackMs, "minBufferVideoMs", "bufferForPlaybackMs");
- assertGreaterOrEqual(
- minBufferAudioMs,
+ minBufferMs,
bufferForPlaybackAfterRebufferMs,
- "minBufferAudioMs",
+ "minBufferMs",
"bufferForPlaybackAfterRebufferMs");
- assertGreaterOrEqual(
- minBufferVideoMs,
- bufferForPlaybackAfterRebufferMs,
- "minBufferVideoMs",
- "bufferForPlaybackAfterRebufferMs");
- assertGreaterOrEqual(maxBufferMs, minBufferAudioMs, "maxBufferMs", "minBufferAudioMs");
- assertGreaterOrEqual(maxBufferMs, minBufferVideoMs, "maxBufferMs", "minBufferVideoMs");
+ assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs");
assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0");
this.allocator = allocator;
- this.minBufferAudioUs = C.msToUs(minBufferAudioMs);
- this.minBufferVideoUs = C.msToUs(minBufferVideoMs);
+ this.minBufferUs = C.msToUs(minBufferMs);
this.maxBufferUs = C.msToUs(maxBufferMs);
this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs);
this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs);
@@ -337,7 +323,7 @@
this.targetBufferBytes =
targetBufferBytesOverwrite != C.LENGTH_UNSET
? targetBufferBytesOverwrite
- : DEFAULT_MUXED_BUFFER_SIZE;
+ : DEFAULT_MIN_BUFFER_SIZE;
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds;
this.backBufferDurationUs = C.msToUs(backBufferDurationMs);
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe;
@@ -351,7 +337,6 @@
@Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
- hasVideo = hasVideo(renderers, trackSelections);
targetBufferBytes =
targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferBytes(renderers, trackSelections)
@@ -387,7 +372,7 @@
@Override
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferBytes;
- long minBufferUs = hasVideo ? minBufferVideoUs : minBufferAudioUs;
+ long minBufferUs = this.minBufferUs;
if (playbackSpeed > 1) {
// The playback speed is faster than real time, so scale up the minimum required media
// duration to keep enough media buffered for a playout duration of minBufferUs.
@@ -399,6 +384,11 @@
minBufferUs = Math.max(minBufferUs, 500_000);
if (bufferedDurationUs < minBufferUs) {
isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
+ if (!isBuffering && bufferedDurationUs < 500_000) {
+ Log.w(
+ "DefaultLoadControl",
+ "Target buffer size reached with less than 500ms of buffered media data.");
+ }
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
isBuffering = false;
} // Else don't change the buffering state
@@ -432,13 +422,13 @@
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType());
}
}
- return targetBufferSize;
+ return Math.max(DEFAULT_MIN_BUFFER_SIZE, targetBufferSize);
}
private void reset(boolean resetAllocator) {
targetBufferBytes =
targetBufferBytesOverwrite == C.LENGTH_UNSET
- ? DEFAULT_MUXED_BUFFER_SIZE
+ ? DEFAULT_MIN_BUFFER_SIZE
: targetBufferBytesOverwrite;
isBuffering = false;
if (resetAllocator) {
@@ -467,15 +457,6 @@
}
}
- private static boolean hasVideo(Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
- for (int i = 0; i < renderers.length; i++) {
- if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelectionArray.get(i) != null) {
- return true;
- }
- }
- return false;
- }
-
private static void assertGreaterOrEqual(int value1, int value2, String name1, String name2) {
Assertions.checkArgument(value1 >= value2, name1 + " cannot be less than " + name2);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
index 1971a4c..5700964 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
@@ -26,22 +26,20 @@
*/
/* package */ final class DefaultMediaClock implements MediaClock {
- /**
- * Listener interface to be notified of changes to the active playback parameters.
- */
- public interface PlaybackParameterListener {
+ /** Listener interface to be notified of changes to the active playback speed. */
+ public interface PlaybackSpeedListener {
/**
- * Called when the active playback parameters changed. Will not be called for {@link
- * #setPlaybackParameters(PlaybackParameters)}.
+ * Called when the active playback speed changed. Will not be called for {@link
+ * #setPlaybackSpeed(float)}.
*
- * @param newPlaybackParameters The newly active {@link PlaybackParameters}.
+ * @param newPlaybackSpeed The newly active playback speed.
*/
- void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters);
+ void onPlaybackSpeedChanged(float newPlaybackSpeed);
}
private final StandaloneMediaClock standaloneClock;
- private final PlaybackParameterListener listener;
+ private final PlaybackSpeedListener listener;
@Nullable private Renderer rendererClockSource;
@Nullable private MediaClock rendererClock;
@@ -49,14 +47,13 @@
private boolean standaloneClockIsStarted;
/**
- * Creates a new instance with listener for playback parameter changes and a {@link Clock} to use
- * for the standalone clock implementation.
+ * Creates a new instance with listener for playback speed changes and a {@link Clock} to use for
+ * the standalone clock implementation.
*
- * @param listener A {@link PlaybackParameterListener} to listen for playback parameter
- * changes.
+ * @param listener A {@link PlaybackSpeedListener} to listen for playback speed changes.
* @param clock A {@link Clock}.
*/
- public DefaultMediaClock(PlaybackParameterListener listener, Clock clock) {
+ public DefaultMediaClock(PlaybackSpeedListener listener, Clock clock) {
this.listener = listener;
this.standaloneClock = new StandaloneMediaClock(clock);
isUsingStandaloneClock = true;
@@ -96,7 +93,7 @@
* clock is already provided.
*/
public void onRendererEnabled(Renderer renderer) throws ExoPlaybackException {
- MediaClock rendererMediaClock = renderer.getMediaClock();
+ @Nullable MediaClock rendererMediaClock = renderer.getMediaClock();
if (rendererMediaClock != null && rendererMediaClock != rendererClock) {
if (rendererClock != null) {
throw ExoPlaybackException.createForUnexpected(
@@ -104,7 +101,7 @@
}
this.rendererClock = rendererMediaClock;
this.rendererClockSource = renderer;
- rendererClock.setPlaybackParameters(standaloneClock.getPlaybackParameters());
+ rendererClock.setPlaybackSpeed(standaloneClock.getPlaybackSpeed());
}
}
@@ -140,19 +137,19 @@
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ public void setPlaybackSpeed(float playbackSpeed) {
if (rendererClock != null) {
- rendererClock.setPlaybackParameters(playbackParameters);
- playbackParameters = rendererClock.getPlaybackParameters();
+ rendererClock.setPlaybackSpeed(playbackSpeed);
+ playbackSpeed = rendererClock.getPlaybackSpeed();
}
- standaloneClock.setPlaybackParameters(playbackParameters);
+ standaloneClock.setPlaybackSpeed(playbackSpeed);
}
@Override
- public PlaybackParameters getPlaybackParameters() {
+ public float getPlaybackSpeed() {
return rendererClock != null
- ? rendererClock.getPlaybackParameters()
- : standaloneClock.getPlaybackParameters();
+ ? rendererClock.getPlaybackSpeed()
+ : standaloneClock.getPlaybackSpeed();
}
private void syncClocks(boolean isReadingAhead) {
@@ -177,10 +174,10 @@
}
// Continuously sync stand-alone clock to renderer clock so that it can take over if needed.
standaloneClock.resetPosition(rendererClockPositionUs);
- PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters();
- if (!playbackParameters.equals(standaloneClock.getPlaybackParameters())) {
- standaloneClock.setPlaybackParameters(playbackParameters);
- listener.onPlaybackParametersChanged(playbackParameters);
+ float playbackSpeed = rendererClock.getPlaybackSpeed();
+ if (playbackSpeed != standaloneClock.getPlaybackSpeed()) {
+ standaloneClock.setPlaybackSpeed(playbackSpeed);
+ listener.onPlaybackSpeedChanged(playbackSpeed);
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
index f547942..ccdddb8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
@@ -20,14 +20,11 @@
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.DefaultAudioSink;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.metadata.MetadataOutput;
@@ -88,10 +85,8 @@
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final Context context;
- @Nullable private DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
@ExtensionRendererMode private int extensionRendererMode;
private long allowedVideoJoiningTimeMs;
- private boolean playClearSamplesWithoutKeys;
private boolean enableDecoderFallback;
private MediaCodecSelector mediaCodecSelector;
@MediaCodecRenderer.MediaCodecOperationMode private int mediaCodecOperationMode;
@@ -106,17 +101,6 @@
}
/**
- * @deprecated Use {@link #DefaultRenderersFactory(Context)} and pass {@link DrmSessionManager}
- * directly to {@link SimpleExoPlayer.Builder}.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public DefaultRenderersFactory(
- Context context, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF);
- }
-
- /**
* @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
* #setExtensionRendererMode(int)}.
*/
@@ -128,47 +112,17 @@
}
/**
- * @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
- * #setExtensionRendererMode(int)}, and pass {@link DrmSessionManager} directly to {@link
- * SimpleExoPlayer.Builder}.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public DefaultRenderersFactory(
- Context context,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- @ExtensionRendererMode int extensionRendererMode) {
- this(context, drmSessionManager, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
- }
-
- /**
* @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
* #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}.
*/
@Deprecated
- @SuppressWarnings("deprecation")
public DefaultRenderersFactory(
Context context,
@ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
- this(context, null, extensionRendererMode, allowedVideoJoiningTimeMs);
- }
-
- /**
- * @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
- * #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}, and pass
- * {@link DrmSessionManager} directly to {@link SimpleExoPlayer.Builder}.
- */
- @Deprecated
- public DefaultRenderersFactory(
- Context context,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- @ExtensionRendererMode int extensionRendererMode,
- long allowedVideoJoiningTimeMs) {
this.context = context;
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
- this.drmSessionManager = drmSessionManager;
mediaCodecSelector = MediaCodecSelector.DEFAULT;
}
@@ -204,25 +158,6 @@
}
/**
- * Sets whether renderers are permitted to play clear regions of encrypted media prior to having
- * obtained the keys necessary to decrypt encrypted regions of the media. For encrypted media that
- * starts with a short clear region, this allows playback to begin in parallel with key
- * acquisition, which can reduce startup latency.
- *
- * <p>The default value is {@code false}.
- *
- * @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
- * encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
- * the media.
- * @return This factory, for convenience.
- */
- public DefaultRenderersFactory setPlayClearSamplesWithoutKeys(
- boolean playClearSamplesWithoutKeys) {
- this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
- return this;
- }
-
- /**
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
* This may result in using a decoder that is less efficient or slower than the primary decoder.
*
@@ -269,18 +204,12 @@
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
- MetadataOutput metadataRendererOutput,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- if (drmSessionManager == null) {
- drmSessionManager = this.drmSessionManager;
- }
+ MetadataOutput metadataRendererOutput) {
ArrayList<Renderer> renderersList = new ArrayList<>();
buildVideoRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
eventHandler,
videoRendererEventListener,
@@ -290,8 +219,6 @@
context,
extensionRendererMode,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
buildAudioProcessors(),
eventHandler,
@@ -312,11 +239,6 @@
* @param context The {@link Context} associated with the player.
* @param extensionRendererMode The extension renderer mode.
* @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
- * not be used for DRM protected playbacks.
- * @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
- * encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
- * the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is slower/less efficient than
* the primary decoder.
@@ -330,8 +252,6 @@
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
boolean enableDecoderFallback,
Handler eventHandler,
VideoRendererEventListener eventListener,
@@ -342,8 +262,6 @@
context,
mediaCodecSelector,
allowedVideoJoiningTimeMs,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
eventHandler,
eventListener,
@@ -412,6 +330,34 @@
// The extension is present, but instantiation failed.
throw new RuntimeException("Error instantiating AV1 extension", e);
}
+
+ try {
+ // Full class names used for constructor args so the LINT rule triggers if any of them move.
+ // LINT.IfChange
+ Class<?> clazz =
+ Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegVideoRenderer");
+ Constructor<?> constructor =
+ clazz.getConstructor(
+ long.class,
+ android.os.Handler.class,
+ com.google.android.exoplayer2.video.VideoRendererEventListener.class,
+ int.class);
+ // LINT.ThenChange(../../../../../../../proguard-rules.txt)
+ Renderer renderer =
+ (Renderer)
+ constructor.newInstance(
+ allowedVideoJoiningTimeMs,
+ eventHandler,
+ eventListener,
+ MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
+ out.add(extensionRendererIndex++, renderer);
+ Log.i(TAG, "Loaded FfmpegVideoRenderer.");
+ } catch (ClassNotFoundException e) {
+ // Expected if the app was built without the extension.
+ } catch (Exception e) {
+ // The extension is present, but instantiation failed.
+ throw new RuntimeException("Error instantiating FFmpeg extension", e);
+ }
}
/**
@@ -420,11 +366,6 @@
* @param context The {@link Context} associated with the player.
* @param extensionRendererMode The extension renderer mode.
* @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
- * not be used for DRM protected playbacks.
- * @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
- * encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
- * the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is slower/less efficient than
* the primary decoder.
@@ -438,8 +379,6 @@
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
boolean enableDecoderFallback,
AudioProcessor[] audioProcessors,
Handler eventHandler,
@@ -449,8 +388,6 @@
new MediaCodecAudioRenderer(
context,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
eventHandler,
eventListener,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
index 653b600..cd9662a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2;
import android.os.SystemClock;
+import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.RendererCapabilities.FormatSupport;
@@ -70,9 +71,10 @@
/** The {@link Type} of the playback failure. */
@Type public final int type;
- /**
- * If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer.
- */
+ /** If {@link #type} is {@link #TYPE_RENDERER}, this is the name of the renderer. */
+ @Nullable public final String rendererName;
+
+ /** If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer. */
public final int rendererIndex;
/**
@@ -116,12 +118,15 @@
*/
public static ExoPlaybackException createForRenderer(
Exception cause,
+ String rendererName,
int rendererIndex,
@Nullable Format rendererFormat,
@FormatSupport int rendererFormatSupport) {
return new ExoPlaybackException(
TYPE_RENDERER,
cause,
+ /* customMessage= */ null,
+ rendererName,
rendererIndex,
rendererFormat,
rendererFormat == null ? RendererCapabilities.FORMAT_HANDLED : rendererFormatSupport);
@@ -161,6 +166,19 @@
this(
type,
cause,
+ /* customMessage= */ null,
+ /* rendererName= */ null,
+ /* rendererIndex= */ C.INDEX_UNSET,
+ /* rendererFormat= */ null,
+ /* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED);
+ }
+
+ private ExoPlaybackException(@Type int type, String message) {
+ this(
+ type,
+ /* cause= */ null,
+ /* customMessage= */ message,
+ /* rendererName= */ null,
/* rendererIndex= */ C.INDEX_UNSET,
/* rendererFormat= */ null,
/* rendererFormatSupport= */ RendererCapabilities.FORMAT_HANDLED);
@@ -168,29 +186,30 @@
private ExoPlaybackException(
@Type int type,
- Throwable cause,
+ @Nullable Throwable cause,
+ @Nullable String customMessage,
+ @Nullable String rendererName,
int rendererIndex,
@Nullable Format rendererFormat,
@FormatSupport int rendererFormatSupport) {
- super(cause);
+ super(
+ deriveMessage(
+ type,
+ customMessage,
+ rendererName,
+ rendererIndex,
+ rendererFormat,
+ rendererFormatSupport),
+ cause);
this.type = type;
this.cause = cause;
+ this.rendererName = rendererName;
this.rendererIndex = rendererIndex;
this.rendererFormat = rendererFormat;
this.rendererFormatSupport = rendererFormatSupport;
timestampMs = SystemClock.elapsedRealtime();
}
- private ExoPlaybackException(@Type int type, String message) {
- super(message);
- this.type = type;
- rendererIndex = C.INDEX_UNSET;
- rendererFormat = null;
- rendererFormatSupport = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
- cause = null;
- timestampMs = SystemClock.elapsedRealtime();
- }
-
/**
* Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}.
*
@@ -230,4 +249,45 @@
Assertions.checkState(type == TYPE_OUT_OF_MEMORY);
return (OutOfMemoryError) Assertions.checkNotNull(cause);
}
+
+ @Nullable
+ private static String deriveMessage(
+ @Type int type,
+ @Nullable String customMessage,
+ @Nullable String rendererName,
+ int rendererIndex,
+ @Nullable Format rendererFormat,
+ @FormatSupport int rendererFormatSupport) {
+ @Nullable String message;
+ switch (type) {
+ case TYPE_SOURCE:
+ message = "Source error";
+ break;
+ case TYPE_RENDERER:
+ message =
+ rendererName
+ + " error"
+ + ", index="
+ + rendererIndex
+ + ", format="
+ + rendererFormat
+ + ", format_supported="
+ + RendererCapabilities.getFormatSupportString(rendererFormatSupport);
+ break;
+ case TYPE_REMOTE:
+ message = "Remote error";
+ break;
+ case TYPE_OUT_OF_MEMORY:
+ message = "Out of memory error";
+ break;
+ case TYPE_UNEXPECTED:
+ default:
+ message = "Unexpected runtime error";
+ break;
+ }
+ if (!TextUtils.isEmpty(customMessage)) {
+ message += ": " + customMessage;
+ }
+ return message;
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java
index f845ece..d779037 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java
@@ -24,8 +24,10 @@
import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.source.ClippingMediaSource;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder;
@@ -138,6 +140,7 @@
private Clock clock;
private TrackSelector trackSelector;
+ private MediaSourceFactory mediaSourceFactory;
private LoadControl loadControl;
private BandwidthMeter bandwidthMeter;
private Looper looper;
@@ -146,6 +149,7 @@
private boolean buildCalled;
private long releaseTimeoutMs;
+ private boolean throwWhenStuckBuffering;
/**
* Creates a builder with a list of {@link Renderer Renderers}.
@@ -154,6 +158,7 @@
*
* <ul>
* <li>{@link TrackSelector}: {@link DefaultTrackSelector}
+ * <li>{@link MediaSourceFactory}: {@link DefaultMediaSourceFactory}
* <li>{@link LoadControl}: {@link DefaultLoadControl}
* <li>{@link BandwidthMeter}: {@link DefaultBandwidthMeter#getSingletonInstance(Context)}
* <li>{@link Looper}: The {@link Looper} associated with the current thread, or the {@link
@@ -171,6 +176,7 @@
this(
renderers,
new DefaultTrackSelector(context),
+ DefaultMediaSourceFactory.newInstance(context),
new DefaultLoadControl(),
DefaultBandwidthMeter.getSingletonInstance(context),
Util.getLooper(),
@@ -188,6 +194,7 @@
*
* @param renderers The {@link Renderer Renderers} to be used by the player.
* @param trackSelector A {@link TrackSelector}.
+ * @param mediaSourceFactory A {@link MediaSourceFactory}.
* @param loadControl A {@link LoadControl}.
* @param bandwidthMeter A {@link BandwidthMeter}.
* @param looper A {@link Looper} that must be used for all calls to the player.
@@ -198,6 +205,7 @@
public Builder(
Renderer[] renderers,
TrackSelector trackSelector,
+ MediaSourceFactory mediaSourceFactory,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
Looper looper,
@@ -207,6 +215,7 @@
Assertions.checkArgument(renderers.length > 0);
this.renderers = renderers;
this.trackSelector = trackSelector;
+ this.mediaSourceFactory = mediaSourceFactory;
this.loadControl = loadControl;
this.bandwidthMeter = bandwidthMeter;
this.looper = looper;
@@ -220,8 +229,7 @@
* ExoPlayer#release()} takes more than {@code timeoutMs} milliseconds to complete, the player
* will raise an error via {@link Player.EventListener#onPlayerError}.
*
- * <p>This method is experimental, and will be renamed or removed in a future release. It should
- * only be called before the player is used.
+ * <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param timeoutMs The time limit in milliseconds, or 0 for no limit.
*/
@@ -231,6 +239,19 @@
}
/**
+ * Sets whether the player should throw when it detects it's stuck buffering.
+ *
+ * <p>This method is experimental, and will be renamed or removed in a future release.
+ *
+ * @param throwWhenStuckBuffering Whether to throw when the player detects it's stuck buffering.
+ * @return This builder.
+ */
+ public Builder experimental_setThrowWhenStuckBuffering(boolean throwWhenStuckBuffering) {
+ this.throwWhenStuckBuffering = throwWhenStuckBuffering;
+ return this;
+ }
+
+ /**
* Sets the {@link TrackSelector} that will be used by the player.
*
* @param trackSelector A {@link TrackSelector}.
@@ -244,6 +265,19 @@
}
/**
+ * Sets the {@link MediaSourceFactory} that will be used by the player.
+ *
+ * @param mediaSourceFactory A {@link MediaSourceFactory}.
+ * @return This builder.
+ * @throws IllegalStateException If {@link #build()} has already been called.
+ */
+ public Builder setMediaSourceFactory(MediaSourceFactory mediaSourceFactory) {
+ Assertions.checkState(!buildCalled);
+ this.mediaSourceFactory = mediaSourceFactory;
+ return this;
+ }
+
+ /**
* Sets the {@link LoadControl} that will be used by the player.
*
* @param loadControl A {@link LoadControl}.
@@ -331,7 +365,7 @@
/**
* Builds an {@link ExoPlayer} instance.
*
- * @throws IllegalStateException If {@link #build()} has already been called.
+ * @throws IllegalStateException If {@code build} has already been called.
*/
public ExoPlayer build() {
Assertions.checkState(!buildCalled);
@@ -340,6 +374,7 @@
new ExoPlayerImpl(
renderers,
trackSelector,
+ mediaSourceFactory,
loadControl,
bandwidthMeter,
analyticsCollector,
@@ -350,6 +385,9 @@
if (releaseTimeoutMs > 0) {
player.experimental_setReleaseTimeoutMs(releaseTimeoutMs);
}
+ if (throwWhenStuckBuffering) {
+ player.experimental_throwWhenStuckBuffering();
+ }
return player;
}
@@ -362,9 +400,6 @@
@Deprecated
void retry();
- /** Prepares the player. */
- void prepare();
-
/** @deprecated Use {@link #setMediaSource(MediaSource)} and {@link #prepare()} instead. */
@Deprecated
void prepare(MediaSource mediaSource);
@@ -463,44 +498,6 @@
void addMediaSources(int index, List<MediaSource> mediaSources);
/**
- * Moves the media item at the current index to the new index.
- *
- * @param currentIndex The current index of the media item to move.
- * @param newIndex The new index of the media item. If the new index is larger than the size of
- * the playlist the item is moved to the end of the playlist.
- */
- void moveMediaItem(int currentIndex, int newIndex);
-
- /**
- * Moves the media item range to the new index.
- *
- * @param fromIndex The start of the range to move.
- * @param toIndex The first item not to be included in the range (exclusive).
- * @param newIndex The new index of the first media item of the range. If the new index is larger
- * than the size of the remaining playlist after removing the range, the range is moved to the
- * end of the playlist.
- */
- void moveMediaItems(int fromIndex, int toIndex, int newIndex);
-
- /**
- * Removes the media item at the given index of the playlist.
- *
- * @param index The index at which to remove the media item.
- */
- void removeMediaItem(int index);
-
- /**
- * Removes a range of media items from the playlist.
- *
- * @param fromIndex The index at which to start removing media items.
- * @param toIndex The index of the first item to be kept (exclusive).
- */
- void removeMediaItems(int fromIndex, int toIndex);
-
- /** Clears the playlist. */
- void clearMediaItems();
-
- /**
* Sets the shuffle order.
*
* @param shuffleOrder The shuffle order.
@@ -557,4 +554,23 @@
* idle state.
*/
void setForegroundMode(boolean foregroundMode);
+
+ /**
+ * Sets whether to pause playback at the end of each media item.
+ *
+ * <p>This means the player will pause at the end of each window in the current {@link
+ * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link
+ * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link
+ * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens.
+ *
+ * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item.
+ */
+ void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems);
+
+ /**
+ * Returns whether the player pauses playback at the end of each media item.
+ *
+ * @see #setPauseAtEndOfMediaItems(boolean)
+ */
+ boolean getPauseAtEndOfMediaItems();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
index add82f2..bb4accc 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
@@ -17,11 +17,8 @@
import android.content.Context;
import android.os.Looper;
-import androidx.annotation.Nullable;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
-import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@@ -35,45 +32,33 @@
private ExoPlayerFactory() {}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
Context context,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) {
RenderersFactory renderersFactory =
new DefaultRenderersFactory(context).setExtensionRendererMode(extensionRendererMode);
- return newSimpleInstance(
- context, renderersFactory, trackSelector, loadControl, drmSessionManager);
+ return newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
Context context,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
RenderersFactory renderersFactory =
new DefaultRenderersFactory(context)
.setExtensionRendererMode(extensionRendererMode)
.setAllowedVideoJoiningTimeMs(allowedVideoJoiningTimeMs);
- return newSimpleInstance(
- context, renderersFactory, trackSelector, loadControl, drmSessionManager);
+ return newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
}
/** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@@ -107,39 +92,6 @@
return newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public static SimpleExoPlayer newSimpleInstance(
- Context context,
- TrackSelector trackSelector,
- LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
- return newSimpleInstance(
- context, renderersFactory, trackSelector, loadControl, drmSessionManager);
- }
-
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public static SimpleExoPlayer newSimpleInstance(
- Context context,
- RenderersFactory renderersFactory,
- TrackSelector trackSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- return newSimpleInstance(
- context, renderersFactory, trackSelector, new DefaultLoadControl(), drmSessionManager);
- }
-
/** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
@@ -153,15 +105,10 @@
renderersFactory,
trackSelector,
loadControl,
- /* drmSessionManager= */ null,
Util.getLooper());
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
@@ -169,41 +116,18 @@
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
- return newSimpleInstance(
- context, renderersFactory, trackSelector, loadControl, drmSessionManager, Util.getLooper());
- }
-
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public static SimpleExoPlayer newSimpleInstance(
- Context context,
- RenderersFactory renderersFactory,
- TrackSelector trackSelector,
- LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
BandwidthMeter bandwidthMeter) {
return newSimpleInstance(
context,
renderersFactory,
trackSelector,
loadControl,
- drmSessionManager,
bandwidthMeter,
new AnalyticsCollector(Clock.DEFAULT),
Util.getLooper());
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
@@ -211,23 +135,17 @@
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector analyticsCollector) {
return newSimpleInstance(
context,
renderersFactory,
trackSelector,
loadControl,
- drmSessionManager,
analyticsCollector,
Util.getLooper());
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
@@ -235,23 +153,17 @@
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
Looper looper) {
return newSimpleInstance(
context,
renderersFactory,
trackSelector,
loadControl,
- drmSessionManager,
new AnalyticsCollector(Clock.DEFAULT),
looper);
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public static SimpleExoPlayer newSimpleInstance(
@@ -259,7 +171,6 @@
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector analyticsCollector,
Looper looper) {
return newSimpleInstance(
@@ -267,17 +178,12 @@
renderersFactory,
trackSelector,
loadControl,
- drmSessionManager,
DefaultBandwidthMeter.getSingletonInstance(context),
analyticsCollector,
looper);
}
- /**
- * @deprecated Use {@link SimpleExoPlayer.Builder} instead. The {@link DrmSessionManager} cannot
- * be passed to {@link SimpleExoPlayer.Builder} and should instead be injected into the {@link
- * MediaSource} factories.
- */
+ /** @deprecated Use {@link SimpleExoPlayer.Builder} instead. */
@SuppressWarnings("deprecation")
@Deprecated
public static SimpleExoPlayer newSimpleInstance(
@@ -285,7 +191,6 @@
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
BandwidthMeter bandwidthMeter,
AnalyticsCollector analyticsCollector,
Looper looper) {
@@ -293,8 +198,8 @@
context,
renderersFactory,
trackSelector,
+ DefaultMediaSourceFactory.newInstance(context),
loadControl,
- drmSessionManager,
bandwidthMeter,
analyticsCollector,
/* useLazyPreparation= */ true,
@@ -348,6 +253,7 @@
return new ExoPlayerImpl(
renderers,
trackSelector,
+ DefaultMediaSourceFactory.newInstance(context),
loadControl,
bandwidthMeter,
/* analyticsCollector= */ null,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
index 53cffce..2242ebf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
@@ -25,6 +25,7 @@
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
@@ -67,20 +68,22 @@
private final CopyOnWriteArrayList<ListenerHolder> listeners;
private final Timeline.Period period;
private final ArrayDeque<Runnable> pendingListenerNotifications;
- private final List<Playlist.MediaSourceHolder> mediaSourceHolders;
+ private final List<MediaSourceList.MediaSourceHolder> mediaSourceHolders;
private final boolean useLazyPreparation;
+ private final MediaSourceFactory mediaSourceFactory;
- private boolean playWhenReady;
- @PlaybackSuppressionReason private int playbackSuppressionReason;
@RepeatMode private int repeatMode;
private boolean shuffleModeEnabled;
private int pendingOperationAcks;
- private boolean hasPendingSeek;
+ private boolean hasPendingDiscontinuity;
+ @DiscontinuityReason private int pendingDiscontinuityReason;
+ @PlayWhenReadyChangeReason private int pendingPlayWhenReadyChangeReason;
private boolean foregroundMode;
- private int pendingSetPlaybackParametersAcks;
- private PlaybackParameters playbackParameters;
+ private int pendingSetPlaybackSpeedAcks;
+ private float playbackSpeed;
private SeekParameters seekParameters;
private ShuffleOrder shuffleOrder;
+ private boolean pauseAtEndOfMediaItems;
// Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo;
@@ -93,15 +96,16 @@
/**
* Constructs an instance. Must be called from a thread that has an associated {@link Looper}.
*
- * @param renderers The {@link Renderer}s that will be used by the instance.
- * @param trackSelector The {@link TrackSelector} that will be used by the instance.
- * @param loadControl The {@link LoadControl} that will be used by the instance.
- * @param bandwidthMeter The {@link BandwidthMeter} that will be used by the instance.
- * @param analyticsCollector The {@link AnalyticsCollector} that will be used by the instance.
+ * @param renderers The {@link Renderer}s.
+ * @param trackSelector The {@link TrackSelector}.
+ * @param mediaSourceFactory The {@link MediaSourceFactory}.
+ * @param loadControl The {@link LoadControl}.
+ * @param bandwidthMeter The {@link BandwidthMeter}.
+ * @param analyticsCollector The {@link AnalyticsCollector}.
* @param useLazyPreparation Whether playlist items are prepared lazily. If false, all manifest
* loads and other initial preparation steps happen immediately. If true, these initial
* preparations are triggered only when the player starts buffering the media.
- * @param clock The {@link Clock} that will be used by the instance.
+ * @param clock The {@link Clock}.
* @param looper The {@link Looper} which must be used for all calls to the player and which is
* used to call listeners on.
*/
@@ -109,6 +113,7 @@
public ExoPlayerImpl(
Renderer[] renderers,
TrackSelector trackSelector,
+ MediaSourceFactory mediaSourceFactory,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
@Nullable AnalyticsCollector analyticsCollector,
@@ -120,10 +125,9 @@
Assertions.checkState(renderers.length > 0);
this.renderers = Assertions.checkNotNull(renderers);
this.trackSelector = Assertions.checkNotNull(trackSelector);
+ this.mediaSourceFactory = mediaSourceFactory;
this.useLazyPreparation = useLazyPreparation;
- playWhenReady = false;
repeatMode = Player.REPEAT_MODE_OFF;
- shuffleModeEnabled = false;
listeners = new CopyOnWriteArrayList<>();
mediaSourceHolders = new ArrayList<>();
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
@@ -133,9 +137,8 @@
new TrackSelection[renderers.length],
null);
period = new Timeline.Period();
- playbackParameters = PlaybackParameters.DEFAULT;
+ playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED;
seekParameters = SeekParameters.DEFAULT;
- playbackSuppressionReason = PLAYBACK_SUPPRESSION_REASON_NONE;
maskingWindowIndex = C.INDEX_UNSET;
eventHandler =
new Handler(looper) {
@@ -156,7 +159,6 @@
emptyTrackSelectorResult,
loadControl,
bandwidthMeter,
- playWhenReady,
repeatMode,
shuffleModeEnabled,
analyticsCollector,
@@ -179,6 +181,16 @@
internalPlayer.experimental_setReleaseTimeoutMs(timeoutMs);
}
+ /**
+ * Configures the player to throw when it detects it's stuck buffering.
+ *
+ * <p>This method is experimental, and will be renamed or removed in a future release. It should
+ * only be called before the player is used.
+ */
+ public void experimental_throwWhenStuckBuffering() {
+ internalPlayer.experimental_throwWhenStuckBuffering();
+ }
+
@Override
@Nullable
public AudioComponent getAudioComponent() {
@@ -204,6 +216,12 @@
}
@Override
+ @Nullable
+ public DeviceComponent getDeviceComponent() {
+ return null;
+ }
+
+ @Override
public Looper getPlaybackLooper() {
return internalPlayer.getPlaybackLooper();
}
@@ -237,12 +255,19 @@
@Override
@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
- return playbackSuppressionReason;
+ return playbackInfo.playbackSuppressionReason;
+ }
+
+ @Deprecated
+ @Override
+ @Nullable
+ public ExoPlaybackException getPlaybackError() {
+ return getPlayerError();
}
@Override
@Nullable
- public ExoPlaybackException getPlaybackError() {
+ public ExoPlaybackException getPlayerError() {
return playbackInfo.playbackError;
}
@@ -276,6 +301,7 @@
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
}
@@ -301,6 +327,12 @@
}
@Override
+ public void setMediaItems(
+ List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs) {
+ setMediaSources(createMediaSources(mediaItems), startWindowIndex, startPositionMs);
+ }
+
+ @Override
public void setMediaSource(MediaSource mediaSource) {
setMediaSources(Collections.singletonList(mediaSource));
}
@@ -323,7 +355,7 @@
@Override
public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) {
- setMediaItemsInternal(
+ setMediaSourcesInternal(
mediaSources,
/* startWindowIndex= */ C.INDEX_UNSET,
/* startPositionMs= */ C.TIME_UNSET,
@@ -333,11 +365,21 @@
@Override
public void setMediaSources(
List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs) {
- setMediaItemsInternal(
+ setMediaSourcesInternal(
mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false);
}
@Override
+ public void addMediaItems(List<MediaItem> mediaItems) {
+ addMediaItems(/* index= */ mediaSourceHolders.size(), mediaItems);
+ }
+
+ @Override
+ public void addMediaItems(int index, List<MediaItem> mediaItems) {
+ addMediaSources(index, createMediaSources(mediaItems));
+ }
+
+ @Override
public void addMediaSource(MediaSource mediaSource) {
addMediaSources(Collections.singletonList(mediaSource));
}
@@ -359,17 +401,17 @@
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++;
- List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
- Timeline timeline =
+ List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
+ PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
internalPlayer.addMediaSources(index, holders, shuffleOrder);
- notifyListeners(
- listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
- }
-
- @Override
- public void removeMediaItem(int index) {
- removeMediaItemsInternal(/* fromIndex= */ index, /* toIndex= */ index + 1);
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ DISCONTINUITY_REASON_INTERNAL,
+ /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ false);
}
@Override
@@ -379,12 +421,6 @@
}
@Override
- public void moveMediaItem(int currentIndex, int newIndex) {
- Assertions.checkArgument(currentIndex != newIndex);
- moveMediaItems(/* fromIndex= */ currentIndex, /* toIndex= */ currentIndex + 1, newIndex);
- }
-
- @Override
public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) {
Assertions.checkArgument(
fromIndex >= 0
@@ -396,12 +432,17 @@
Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++;
newFromIndex = Math.min(newFromIndex, mediaSourceHolders.size() - (toIndex - fromIndex));
- Playlist.moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
- Timeline timeline =
+ MediaSourceList.moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
+ PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
- notifyListeners(
- listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ DISCONTINUITY_REASON_INTERNAL,
+ /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ false);
}
@Override
@@ -414,55 +455,67 @@
@Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
+ PlaybackInfo playbackInfo = maskTimeline();
+ maskWithCurrentPosition();
pendingOperationAcks++;
this.shuffleOrder = shuffleOrder;
- Timeline timeline = maskTimeline();
internalPlayer.setShuffleOrder(shuffleOrder);
- notifyListeners(
- listener -> listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ DISCONTINUITY_REASON_INTERNAL,
+ /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ false);
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
- setPlayWhenReady(playWhenReady, PLAYBACK_SUPPRESSION_REASON_NONE);
+ setPlayWhenReady(
+ playWhenReady,
+ PLAYBACK_SUPPRESSION_REASON_NONE,
+ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
+ }
+
+ @Override
+ public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
+ if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) {
+ return;
+ }
+ this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
+ internalPlayer.setPauseAtEndOfWindow(pauseAtEndOfMediaItems);
+ }
+
+ @Override
+ public boolean getPauseAtEndOfMediaItems() {
+ return pauseAtEndOfMediaItems;
}
public void setPlayWhenReady(
- boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
- boolean oldIsPlaying = isPlaying();
- boolean oldInternalPlayWhenReady =
- this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
- boolean internalPlayWhenReady =
- playWhenReady && playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
- if (oldInternalPlayWhenReady != internalPlayWhenReady) {
- internalPlayer.setPlayWhenReady(internalPlayWhenReady);
+ boolean playWhenReady,
+ @PlaybackSuppressionReason int playbackSuppressionReason,
+ @PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
+ if (playbackInfo.playWhenReady == playWhenReady
+ && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) {
+ return;
}
- boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
- boolean suppressionReasonChanged = this.playbackSuppressionReason != playbackSuppressionReason;
- this.playWhenReady = playWhenReady;
- this.playbackSuppressionReason = playbackSuppressionReason;
- boolean isPlaying = isPlaying();
- boolean isPlayingChanged = oldIsPlaying != isPlaying;
- if (playWhenReadyChanged || suppressionReasonChanged || isPlayingChanged) {
- int playbackState = playbackInfo.playbackState;
- notifyListeners(
- listener -> {
- if (playWhenReadyChanged) {
- listener.onPlayerStateChanged(playWhenReady, playbackState);
- }
- if (suppressionReasonChanged) {
- listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);
- }
- if (isPlayingChanged) {
- listener.onIsPlayingChanged(isPlaying);
- }
- });
- }
+ maskWithCurrentPosition();
+ pendingOperationAcks++;
+ PlaybackInfo playbackInfo =
+ this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
+ internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ DISCONTINUITY_REASON_INTERNAL,
+ /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ playWhenReadyChangeReason,
+ /* seekProcessed= */ false);
}
@Override
public boolean getPlayWhenReady() {
- return playWhenReady;
+ return playbackInfo.playWhenReady;
}
@Override
@@ -504,7 +557,6 @@
if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) {
throw new IllegalSeekPositionException(timeline, windowIndex, positionMs);
}
- hasPendingSeek = true;
pendingOperationAcks++;
if (isPlayingAd()) {
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
@@ -521,28 +573,58 @@
return;
}
maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs);
+ @Player.State
+ int newPlaybackState =
+ getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING;
+ PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackState(newPlaybackState);
internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs));
- notifyListeners(listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK));
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ true,
+ /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
+ /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ true);
}
+ /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
- if (playbackParameters == null) {
- playbackParameters = PlaybackParameters.DEFAULT;
- }
- if (this.playbackParameters.equals(playbackParameters)) {
+ setPlaybackSpeed(
+ playbackParameters != null ? playbackParameters.speed : Player.DEFAULT_PLAYBACK_SPEED);
+ }
+
+ /** @deprecated Use {@link #getPlaybackSpeed()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public PlaybackParameters getPlaybackParameters() {
+ return new PlaybackParameters(playbackSpeed);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setPlaybackSpeed(float playbackSpeed) {
+ Assertions.checkState(playbackSpeed > 0);
+ if (this.playbackSpeed == playbackSpeed) {
return;
}
- pendingSetPlaybackParametersAcks++;
- this.playbackParameters = playbackParameters;
- internalPlayer.setPlaybackParameters(playbackParameters);
- PlaybackParameters playbackParametersToNotify = playbackParameters;
- notifyListeners(listener -> listener.onPlaybackParametersChanged(playbackParametersToNotify));
+ pendingSetPlaybackSpeedAcks++;
+ this.playbackSpeed = playbackSpeed;
+ PlaybackParameters playbackParameters = new PlaybackParameters(playbackSpeed);
+ internalPlayer.setPlaybackSpeed(playbackSpeed);
+ notifyListeners(
+ listener -> {
+ listener.onPlaybackParametersChanged(playbackParameters);
+ listener.onPlaybackSpeedChanged(playbackSpeed);
+ });
}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return playbackParameters;
+ public float getPlaybackSpeed() {
+ return playbackSpeed;
}
@Override
@@ -587,6 +669,7 @@
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
/* seekProcessed= */ false);
}
@@ -691,9 +774,9 @@
public long getContentPosition() {
if (isPlayingAd()) {
playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period);
- return playbackInfo.contentPositionUs == C.TIME_UNSET
+ return playbackInfo.requestedContentPositionUs == C.TIME_UNSET
? playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDefaultPositionMs()
- : period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs);
+ : period.getPositionInWindowMs() + C.usToMs(playbackInfo.requestedContentPositionUs);
} else {
return getCurrentPosition();
}
@@ -749,17 +832,12 @@
// Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) {
-
switch (msg.what) {
case ExoPlayerImplInternal.MSG_PLAYBACK_INFO_CHANGED:
- handlePlaybackInfo(
- /* playbackInfo= */ (PlaybackInfo) msg.obj,
- /* operationAcks= */ msg.arg1,
- /* positionDiscontinuity= */ msg.arg2 != C.INDEX_UNSET,
- /* positionDiscontinuityReason= */ msg.arg2);
+ handlePlaybackInfo((ExoPlayerImplInternal.PlaybackInfoUpdate) msg.obj);
break;
- case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED:
- handlePlaybackParameters((PlaybackParameters) msg.obj, /* operationAck= */ msg.arg1 != 0);
+ case ExoPlayerImplInternal.MSG_PLAYBACK_SPEED_CHANGED:
+ handlePlaybackSpeed((Float) msg.obj, /* operationAck= */ msg.arg1 != 0);
break;
default:
throw new IllegalStateException();
@@ -775,38 +853,55 @@
}
}
- private void handlePlaybackParameters(
- PlaybackParameters playbackParameters, boolean operationAck) {
- if (operationAck) {
- pendingSetPlaybackParametersAcks--;
+ private List<MediaSource> createMediaSources(List<MediaItem> mediaItems) {
+ List<MediaSource> mediaSources = new ArrayList<>();
+ for (int i = 0; i < mediaItems.size(); i++) {
+ mediaSources.add(mediaSourceFactory.createMediaSource(mediaItems.get(i)));
}
- if (pendingSetPlaybackParametersAcks == 0) {
- if (!this.playbackParameters.equals(playbackParameters)) {
- this.playbackParameters = playbackParameters;
- notifyListeners(listener -> listener.onPlaybackParametersChanged(playbackParameters));
+ return mediaSources;
+ }
+
+ @SuppressWarnings("deprecation")
+ private void handlePlaybackSpeed(float playbackSpeed, boolean operationAck) {
+ if (operationAck) {
+ pendingSetPlaybackSpeedAcks--;
+ }
+ if (pendingSetPlaybackSpeedAcks == 0) {
+ if (this.playbackSpeed != playbackSpeed) {
+ this.playbackSpeed = playbackSpeed;
+ notifyListeners(
+ listener -> {
+ listener.onPlaybackParametersChanged(new PlaybackParameters(playbackSpeed));
+ listener.onPlaybackSpeedChanged(playbackSpeed);
+ });
}
}
}
- private void handlePlaybackInfo(
- PlaybackInfo playbackInfo,
- int operationAcks,
- boolean positionDiscontinuity,
- @DiscontinuityReason int positionDiscontinuityReason) {
- pendingOperationAcks -= operationAcks;
+ private void handlePlaybackInfo(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate) {
+ pendingOperationAcks -= playbackInfoUpdate.operationAcks;
+ if (playbackInfoUpdate.positionDiscontinuity) {
+ hasPendingDiscontinuity = true;
+ pendingDiscontinuityReason = playbackInfoUpdate.discontinuityReason;
+ }
+ if (playbackInfoUpdate.hasPlayWhenReadyChangeReason) {
+ pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason;
+ }
if (pendingOperationAcks == 0) {
- if (!this.playbackInfo.timeline.isEmpty() && playbackInfo.timeline.isEmpty()) {
+ if (!this.playbackInfo.timeline.isEmpty()
+ && playbackInfoUpdate.playbackInfo.timeline.isEmpty()) {
// Update the masking variables, which are used when the timeline becomes empty.
resetMaskingPosition();
}
- boolean seekProcessed = hasPendingSeek;
- hasPendingSeek = false;
+ boolean positionDiscontinuity = hasPendingDiscontinuity;
+ hasPendingDiscontinuity = false;
updatePlaybackInfo(
- playbackInfo,
+ playbackInfoUpdate.playbackInfo,
positionDiscontinuity,
- positionDiscontinuityReason,
+ pendingDiscontinuityReason,
TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
- seekProcessed);
+ pendingPlayWhenReadyChangeReason,
+ /* seekProcessed= */ false);
}
}
@@ -818,30 +913,30 @@
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
resetMaskingPosition();
} else {
- maskingWindowIndex = getCurrentWindowIndex();
- maskingPeriodIndex = getCurrentPeriodIndex();
- maskingWindowPositionMs = getCurrentPosition();
+ maskWithCurrentPosition();
}
Timeline timeline = playbackInfo.timeline;
MediaPeriodId mediaPeriodId = playbackInfo.periodId;
- long contentPositionUs = playbackInfo.contentPositionUs;
+ long requestedContentPositionUs = playbackInfo.requestedContentPositionUs;
long positionUs = playbackInfo.positionUs;
if (clearPlaylist) {
timeline = Timeline.EMPTY;
mediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline();
- contentPositionUs = C.TIME_UNSET;
+ requestedContentPositionUs = C.TIME_UNSET;
positionUs = 0;
}
return new PlaybackInfo(
timeline,
mediaPeriodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
resetError ? null : playbackInfo.playbackError,
/* isLoading= */ false,
clearPlaylist ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
clearPlaylist ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
mediaPeriodId,
+ playbackInfo.playWhenReady,
+ playbackInfo.playbackSuppressionReason,
positionUs,
/* totalBufferedDurationUs= */ 0,
positionUs);
@@ -852,12 +947,11 @@
boolean positionDiscontinuity,
@DiscontinuityReason int positionDiscontinuityReason,
@TimelineChangeReason int timelineChangeReason,
+ @PlayWhenReadyChangeReason int playWhenReadyChangeReason,
boolean seekProcessed) {
- boolean previousIsPlaying = isPlaying();
// Assign playback info immediately such that all getters return the right values.
PlaybackInfo previousPlaybackInfo = this.playbackInfo;
this.playbackInfo = playbackInfo;
- boolean isPlaying = isPlaying();
notifyListeners(
new PlaybackInfoUpdate(
playbackInfo,
@@ -867,26 +961,26 @@
positionDiscontinuity,
positionDiscontinuityReason,
timelineChangeReason,
- seekProcessed,
- playWhenReady,
- /* isPlayingChanged= */ previousIsPlaying != isPlaying));
+ playWhenReadyChangeReason,
+ seekProcessed));
}
- private void setMediaItemsInternal(
+ private void setMediaSourcesInternal(
List<MediaSource> mediaItems,
int startWindowIndex,
long startPositionMs,
boolean resetToDefaultPosition) {
int currentWindowIndex = getCurrentWindowIndexInternal();
long currentPositionMs = getCurrentPosition();
- boolean currentPlayWhenReady = getPlayWhenReady();
pendingOperationAcks++;
if (!mediaSourceHolders.isEmpty()) {
removeMediaSourceHolders(
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
}
- List<Playlist.MediaSourceHolder> holders = addMediaSourceHolders(/* index= */ 0, mediaItems);
- Timeline timeline = maskTimeline();
+ List<MediaSourceList.MediaSourceHolder> holders =
+ addMediaSourceHolders(/* index= */ 0, mediaItems);
+ PlaybackInfo playbackInfo = maskTimeline();
+ Timeline timeline = playbackInfo.timeline;
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs);
}
@@ -900,9 +994,9 @@
}
maskWindowIndexAndPositionForSeek(
timeline, startWindowIndex == C.INDEX_UNSET ? 0 : startWindowIndex, startPositionMs);
- // mask the playback state
+ // Mask the playback state.
int maskingPlaybackState = playbackInfo.playbackState;
- if (startWindowIndex != C.INDEX_UNSET) {
+ if (startWindowIndex != C.INDEX_UNSET && playbackInfo.playbackState != STATE_IDLE) {
// Position reset to startWindowIndex (results in pending initial seek).
if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) {
// Setting an empty timeline or invalid seek transitions to ended.
@@ -911,30 +1005,24 @@
maskingPlaybackState = STATE_BUFFERING;
}
}
- boolean playbackStateChanged =
- playbackInfo.playbackState != STATE_IDLE
- && playbackInfo.playbackState != maskingPlaybackState;
- int finalMaskingPlaybackState = maskingPlaybackState;
- if (playbackStateChanged) {
- playbackInfo = playbackInfo.copyWithPlaybackState(finalMaskingPlaybackState);
- }
+ playbackInfo = playbackInfo.copyWithPlaybackState(maskingPlaybackState);
internalPlayer.setMediaSources(
holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder);
- notifyListeners(
- listener -> {
- listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
- if (playbackStateChanged) {
- listener.onPlayerStateChanged(currentPlayWhenReady, finalMaskingPlaybackState);
- }
- });
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
+ /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ false);
}
- private List<Playlist.MediaSourceHolder> addMediaSourceHolders(
+ private List<MediaSourceList.MediaSourceHolder> addMediaSourceHolders(
int index, List<MediaSource> mediaSources) {
- List<Playlist.MediaSourceHolder> holders = new ArrayList<>();
+ List<MediaSourceList.MediaSourceHolder> holders = new ArrayList<>();
for (int i = 0; i < mediaSources.size(); i++) {
- Playlist.MediaSourceHolder holder =
- new Playlist.MediaSourceHolder(mediaSources.get(i), useLazyPreparation);
+ MediaSourceList.MediaSourceHolder holder =
+ new MediaSourceList.MediaSourceHolder(mediaSources.get(i), useLazyPreparation);
holders.add(holder);
mediaSourceHolders.add(i + index, holder);
}
@@ -949,12 +1037,11 @@
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolders.size());
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
- boolean currentPlayWhenReady = getPlayWhenReady();
Timeline oldTimeline = getCurrentTimeline();
int currentMediaSourceCount = mediaSourceHolders.size();
pendingOperationAcks++;
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
- Timeline timeline =
+ PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
// Player transitions to STATE_ENDED if the current index is part of the removed tail.
final boolean transitionsToEnded =
@@ -962,23 +1049,23 @@
&& playbackInfo.playbackState != STATE_ENDED
&& fromIndex < toIndex
&& toIndex == currentMediaSourceCount
- && currentWindowIndex >= timeline.getWindowCount();
+ && currentWindowIndex >= playbackInfo.timeline.getWindowCount();
if (transitionsToEnded) {
playbackInfo = playbackInfo.copyWithPlaybackState(STATE_ENDED);
}
internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder);
- notifyListeners(
- listener -> {
- listener.onTimelineChanged(timeline, TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
- if (transitionsToEnded) {
- listener.onPlayerStateChanged(currentPlayWhenReady, STATE_ENDED);
- }
- });
+ updatePlaybackInfo(
+ playbackInfo,
+ /* positionDiscontinuity= */ false,
+ /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
+ /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
+ /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ /* seekProcessed= */ false);
}
- private List<Playlist.MediaSourceHolder> removeMediaSourceHolders(
+ private List<MediaSourceList.MediaSourceHolder> removeMediaSourceHolders(
int fromIndex, int toIndexExclusive) {
- List<Playlist.MediaSourceHolder> removed = new ArrayList<>();
+ List<MediaSourceList.MediaSourceHolder> removed = new ArrayList<>();
for (int i = toIndexExclusive - 1; i >= fromIndex; i--) {
removed.add(mediaSourceHolders.remove(i));
}
@@ -986,18 +1073,17 @@
return removed;
}
- private Timeline maskTimeline() {
- playbackInfo =
- playbackInfo.copyWithTimeline(
- mediaSourceHolders.isEmpty()
- ? Timeline.EMPTY
- : new Playlist.PlaylistTimeline(mediaSourceHolders, shuffleOrder));
- return playbackInfo.timeline;
+ private PlaybackInfo maskTimeline() {
+ return playbackInfo.copyWithTimeline(
+ mediaSourceHolders.isEmpty()
+ ? Timeline.EMPTY
+ : new MediaSourceList.PlaylistTimeline(mediaSourceHolders, shuffleOrder));
}
- private Timeline maskTimelineAndWindowIndex(
+ private PlaybackInfo maskTimelineAndWindowIndex(
int currentWindowIndex, long currentPositionMs, Timeline oldTimeline) {
- Timeline maskingTimeline = maskTimeline();
+ PlaybackInfo playbackInfo = maskTimeline();
+ Timeline maskingTimeline = playbackInfo.timeline;
if (oldTimeline.isEmpty()) {
// The index is the default index or was set by a seek in the empty old timeline.
maskingWindowIndex = currentWindowIndex;
@@ -1005,7 +1091,7 @@
// The seek is not valid in the new timeline.
maskWithDefaultPosition(maskingTimeline);
}
- return maskingTimeline;
+ return playbackInfo;
}
@Nullable
Pair<Object, Long> periodPosition =
@@ -1043,7 +1129,7 @@
maskWithDefaultPosition(maskingTimeline);
}
}
- return maskingTimeline;
+ return playbackInfo;
}
private void maskWindowIndexAndPositionForSeek(
@@ -1067,6 +1153,12 @@
}
}
+ private void maskWithCurrentPosition() {
+ maskingWindowIndex = getCurrentWindowIndexInternal();
+ maskingPeriodIndex = getCurrentPeriodIndex();
+ maskingWindowPositionMs = getCurrentPosition();
+ }
+
private void maskWithDefaultPosition(Timeline timeline) {
if (timeline.isEmpty()) {
resetMaskingPosition();
@@ -1118,16 +1210,18 @@
private final CopyOnWriteArrayList<ListenerHolder> listenerSnapshot;
private final TrackSelector trackSelector;
private final boolean positionDiscontinuity;
- private final @Player.DiscontinuityReason int positionDiscontinuityReason;
- private final int timelineChangeReason;
+ @DiscontinuityReason private final int positionDiscontinuityReason;
+ @TimelineChangeReason private final int timelineChangeReason;
+ @PlayWhenReadyChangeReason private final int playWhenReadyChangeReason;
private final boolean seekProcessed;
private final boolean playbackStateChanged;
private final boolean playbackErrorChanged;
private final boolean timelineChanged;
private final boolean isLoadingChanged;
private final boolean trackSelectorResultChanged;
- private final boolean playWhenReady;
private final boolean isPlayingChanged;
+ private final boolean playWhenReadyChanged;
+ private final boolean playbackSuppressionReasonChanged;
public PlaybackInfoUpdate(
PlaybackInfo playbackInfo,
@@ -1137,18 +1231,16 @@
boolean positionDiscontinuity,
@DiscontinuityReason int positionDiscontinuityReason,
@TimelineChangeReason int timelineChangeReason,
- boolean seekProcessed,
- boolean playWhenReady,
- boolean isPlayingChanged) {
+ @PlayWhenReadyChangeReason int playWhenReadyChangeReason,
+ boolean seekProcessed) {
this.playbackInfo = playbackInfo;
this.listenerSnapshot = new CopyOnWriteArrayList<>(listeners);
this.trackSelector = trackSelector;
this.positionDiscontinuity = positionDiscontinuity;
this.positionDiscontinuityReason = positionDiscontinuityReason;
this.timelineChangeReason = timelineChangeReason;
+ this.playWhenReadyChangeReason = playWhenReadyChangeReason;
this.seekProcessed = seekProcessed;
- this.playWhenReady = playWhenReady;
- this.isPlayingChanged = isPlayingChanged;
playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState;
playbackErrorChanged =
previousPlaybackInfo.playbackError != playbackInfo.playbackError
@@ -1157,8 +1249,13 @@
timelineChanged = !previousPlaybackInfo.timeline.equals(playbackInfo.timeline);
trackSelectorResultChanged =
previousPlaybackInfo.trackSelectorResult != playbackInfo.trackSelectorResult;
+ playWhenReadyChanged = previousPlaybackInfo.playWhenReady != playbackInfo.playWhenReady;
+ playbackSuppressionReasonChanged =
+ previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason;
+ isPlayingChanged = isPlaying(previousPlaybackInfo) != isPlaying(playbackInfo);
}
+ @SuppressWarnings("deprecation")
@Override
public void run() {
if (timelineChanged) {
@@ -1183,23 +1280,49 @@
playbackInfo.trackGroups, playbackInfo.trackSelectorResult.selections));
}
if (isLoadingChanged) {
- invokeAll(listenerSnapshot, listener -> listener.onLoadingChanged(playbackInfo.isLoading));
+ invokeAll(
+ listenerSnapshot, listener -> listener.onIsLoadingChanged(playbackInfo.isLoading));
+ }
+ if (playbackStateChanged || playWhenReadyChanged) {
+ invokeAll(
+ listenerSnapshot,
+ listener ->
+ listener.onPlayerStateChanged(
+ playbackInfo.playWhenReady, playbackInfo.playbackState));
}
if (playbackStateChanged) {
invokeAll(
listenerSnapshot,
- listener -> listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState));
+ listener -> listener.onPlaybackStateChanged(playbackInfo.playbackState));
}
- if (isPlayingChanged) {
+ if (playWhenReadyChanged) {
invokeAll(
listenerSnapshot,
listener ->
- listener.onIsPlayingChanged(playbackInfo.playbackState == Player.STATE_READY));
+ listener.onPlayWhenReadyChanged(
+ playbackInfo.playWhenReady, playWhenReadyChangeReason));
+ }
+ if (playbackSuppressionReasonChanged) {
+ invokeAll(
+ listenerSnapshot,
+ listener ->
+ listener.onPlaybackSuppressionReasonChanged(
+ playbackInfo.playbackSuppressionReason));
+ }
+ if (isPlayingChanged) {
+ invokeAll(
+ listenerSnapshot, listener -> listener.onIsPlayingChanged(isPlaying(playbackInfo)));
}
if (seekProcessed) {
invokeAll(listenerSnapshot, EventListener::onSeekProcessed);
}
}
+
+ private static boolean isPlaying(PlaybackInfo playbackInfo) {
+ return playbackInfo.playbackState == Player.STATE_READY
+ && playbackInfo.playWhenReady
+ && playbackInfo.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
+ }
}
private static void invokeAll(
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
index 9f9fed6..56cce6d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
@@ -24,8 +24,10 @@
import android.util.Pair;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
+import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
+import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason;
+import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.source.MediaPeriod;
@@ -54,22 +56,22 @@
implements Handler.Callback,
MediaPeriod.Callback,
TrackSelector.InvalidationListener,
- Playlist.PlaylistInfoRefreshListener,
- PlaybackParameterListener,
+ MediaSourceList.MediaSourceListInfoRefreshListener,
+ PlaybackSpeedListener,
PlayerMessage.Sender {
private static final String TAG = "ExoPlayerImplInternal";
// External messages
public static final int MSG_PLAYBACK_INFO_CHANGED = 0;
- public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 1;
+ public static final int MSG_PLAYBACK_SPEED_CHANGED = 1;
// Internal messages
private static final int MSG_PREPARE = 0;
private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2;
private static final int MSG_SEEK_TO = 3;
- private static final int MSG_SET_PLAYBACK_PARAMETERS = 4;
+ private static final int MSG_SET_PLAYBACK_SPEED = 4;
private static final int MSG_SET_SEEK_PARAMETERS = 5;
private static final int MSG_STOP = 6;
private static final int MSG_RELEASE = 7;
@@ -81,13 +83,14 @@
private static final int MSG_SET_FOREGROUND_MODE = 13;
private static final int MSG_SEND_MESSAGE = 14;
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15;
- private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16;
+ private static final int MSG_PLAYBACK_SPEED_CHANGED_INTERNAL = 16;
private static final int MSG_SET_MEDIA_SOURCES = 17;
private static final int MSG_ADD_MEDIA_SOURCES = 18;
private static final int MSG_MOVE_MEDIA_SOURCES = 19;
private static final int MSG_REMOVE_MEDIA_SOURCES = 20;
private static final int MSG_SET_SHUFFLE_ORDER = 21;
private static final int MSG_PLAYLIST_UPDATE_REQUESTED = 22;
+ private static final int MSG_SET_PAUSE_AT_END_OF_WINDOW = 23;
private static final int ACTIVE_INTERVAL_MS = 10;
private static final int IDLE_INTERVAL_MS = 1000;
@@ -106,31 +109,33 @@
private final long backBufferDurationUs;
private final boolean retainBackBufferFromKeyframe;
private final DefaultMediaClock mediaClock;
- private final PlaybackInfoUpdate playbackInfoUpdate;
private final ArrayList<PendingMessageInfo> pendingMessages;
private final Clock clock;
private final MediaPeriodQueue queue;
- private final Playlist playlist;
+ private final MediaSourceList mediaSourceList;
@SuppressWarnings("unused")
private SeekParameters seekParameters;
private PlaybackInfo playbackInfo;
- private Renderer[] enabledRenderers;
+ private PlaybackInfoUpdate playbackInfoUpdate;
private boolean released;
- private boolean playWhenReady;
+ private boolean pauseAtEndOfWindow;
+ private boolean pendingPauseAtEndOfPeriod;
private boolean rebuffering;
private boolean shouldContinueLoading;
@Player.RepeatMode private int repeatMode;
private boolean shuffleModeEnabled;
private boolean foregroundMode;
+ private int enabledRendererCount;
@Nullable private SeekPosition pendingInitialSeekPosition;
private long rendererPositionUs;
private int nextPendingMessageIndex;
private boolean deliverPendingMessageAtStartPositionRequired;
private long releaseTimeoutMs;
+ private boolean throwWhenStuckBuffering;
public ExoPlayerImplInternal(
Renderer[] renderers,
@@ -138,7 +143,6 @@
TrackSelectorResult emptyTrackSelectorResult,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
- boolean playWhenReady,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled,
@Nullable AnalyticsCollector analyticsCollector,
@@ -149,7 +153,6 @@
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
this.loadControl = loadControl;
this.bandwidthMeter = bandwidthMeter;
- this.playWhenReady = playWhenReady;
this.repeatMode = repeatMode;
this.shuffleModeEnabled = shuffleModeEnabled;
this.eventHandler = eventHandler;
@@ -161,7 +164,7 @@
seekParameters = SeekParameters.DEFAULT;
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
- playbackInfoUpdate = new PlaybackInfoUpdate();
+ playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
rendererCapabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
renderers[i].setIndex(i);
@@ -169,21 +172,19 @@
}
mediaClock = new DefaultMediaClock(this, clock);
pendingMessages = new ArrayList<>();
- enabledRenderers = new Renderer[0];
window = new Timeline.Window();
period = new Timeline.Period();
trackSelector.init(/* listener= */ this, bandwidthMeter);
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect.
- internalPlaybackThread =
- new HandlerThread("ExoPlayerImplInternal:Handler", Process.THREAD_PRIORITY_AUDIO);
+ internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
deliverPendingMessageAtStartPositionRequired = true;
- playlist = new Playlist(this);
+ mediaSourceList = new MediaSourceList(this);
if (analyticsCollector != null) {
- playlist.setAnalyticsCollector(eventHandler, analyticsCollector);
+ mediaSourceList.setAnalyticsCollector(eventHandler, analyticsCollector);
}
}
@@ -191,12 +192,25 @@
this.releaseTimeoutMs = releaseTimeoutMs;
}
+ public void experimental_throwWhenStuckBuffering() {
+ throwWhenStuckBuffering = true;
+ }
+
public void prepare() {
handler.obtainMessage(MSG_PREPARE).sendToTarget();
}
- public void setPlayWhenReady(boolean playWhenReady) {
- handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget();
+ public void setPlayWhenReady(
+ boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
+ handler
+ .obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, playbackSuppressionReason)
+ .sendToTarget();
+ }
+
+ public void setPauseAtEndOfWindow(boolean pauseAtEndOfWindow) {
+ handler
+ .obtainMessage(MSG_SET_PAUSE_AT_END_OF_WINDOW, pauseAtEndOfWindow ? 1 : 0, /* ignored */ 0)
+ .sendToTarget();
}
public void setRepeatMode(@Player.RepeatMode int repeatMode) {
@@ -213,8 +227,8 @@
.sendToTarget();
}
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
- handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
+ public void setPlaybackSpeed(float playbackSpeed) {
+ handler.obtainMessage(MSG_SET_PLAYBACK_SPEED, playbackSpeed).sendToTarget();
}
public void setSeekParameters(SeekParameters seekParameters) {
@@ -226,25 +240,25 @@
}
public void setMediaSources(
- List<Playlist.MediaSourceHolder> mediaSources,
+ List<MediaSourceList.MediaSourceHolder> mediaSources,
int windowIndex,
long positionUs,
ShuffleOrder shuffleOrder) {
handler
.obtainMessage(
MSG_SET_MEDIA_SOURCES,
- new PlaylistUpdateMessage(mediaSources, shuffleOrder, windowIndex, positionUs))
+ new MediaSourceListUpdateMessage(mediaSources, shuffleOrder, windowIndex, positionUs))
.sendToTarget();
}
public void addMediaSources(
- int index, List<Playlist.MediaSourceHolder> mediaSources, ShuffleOrder shuffleOrder) {
+ int index, List<MediaSourceList.MediaSourceHolder> mediaSources, ShuffleOrder shuffleOrder) {
handler
.obtainMessage(
MSG_ADD_MEDIA_SOURCES,
index,
/* ignored */ 0,
- new PlaylistUpdateMessage(
+ new MediaSourceListUpdateMessage(
mediaSources,
shuffleOrder,
/* windowIndex= */ C.INDEX_UNSET,
@@ -354,17 +368,16 @@
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
}
- // DefaultMediaClock.PlaybackParameterListener implementation.
+ // DefaultMediaClock.PlaybackSpeedListener implementation.
@Override
- public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
- sendPlaybackParametersChangedInternal(playbackParameters, /* acknowledgeCommand= */ false);
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
+ sendPlaybackSpeedChangedInternal(playbackSpeed, /* acknowledgeCommand= */ false);
}
// Handler.Callback implementation.
@Override
- @SuppressWarnings("unchecked")
public boolean handleMessage(Message msg) {
try {
switch (msg.what) {
@@ -372,7 +385,11 @@
prepareInternal();
break;
case MSG_SET_PLAY_WHEN_READY:
- setPlayWhenReadyInternal(msg.arg1 != 0);
+ setPlayWhenReadyInternal(
+ /* playWhenReady= */ msg.arg1 != 0,
+ /* playbackSuppressionReason= */ msg.arg2,
+ /* operationAck= */ true,
+ Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
break;
case MSG_SET_REPEAT_MODE:
setRepeatModeInternal(msg.arg1);
@@ -386,8 +403,8 @@
case MSG_SEEK_TO:
seekToInternal((SeekPosition) msg.obj);
break;
- case MSG_SET_PLAYBACK_PARAMETERS:
- setPlaybackParametersInternal((PlaybackParameters) msg.obj);
+ case MSG_SET_PLAYBACK_SPEED:
+ setPlaybackSpeedInternal((Float) msg.obj);
break;
case MSG_SET_SEEK_PARAMETERS:
setSeekParametersInternal((SeekParameters) msg.obj);
@@ -411,9 +428,8 @@
case MSG_TRACK_SELECTION_INVALIDATED:
reselectTracksInternal();
break;
- case MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL:
- handlePlaybackParameters(
- (PlaybackParameters) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
+ case MSG_PLAYBACK_SPEED_CHANGED_INTERNAL:
+ handlePlaybackSpeed((Float) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
break;
case MSG_SEND_MESSAGE:
sendMessageInternal((PlayerMessage) msg.obj);
@@ -422,10 +438,10 @@
sendMessageToTargetThread((PlayerMessage) msg.obj);
break;
case MSG_SET_MEDIA_SOURCES:
- setMediaItemsInternal((PlaylistUpdateMessage) msg.obj);
+ setMediaItemsInternal((MediaSourceListUpdateMessage) msg.obj);
break;
case MSG_ADD_MEDIA_SOURCES:
- addMediaItemsInternal((PlaylistUpdateMessage) msg.obj, msg.arg1);
+ addMediaItemsInternal((MediaSourceListUpdateMessage) msg.obj, msg.arg1);
break;
case MSG_MOVE_MEDIA_SOURCES:
moveMediaItemsInternal((MoveMediaItemsMessage) msg.obj);
@@ -437,7 +453,10 @@
setShuffleOrderInternal((ShuffleOrder) msg.obj);
break;
case MSG_PLAYLIST_UPDATE_REQUESTED:
- playlistUpdateRequestedInternal();
+ mediaSourceListUpdateRequestedInternal();
+ break;
+ case MSG_SET_PAUSE_AT_END_OF_WINDOW:
+ setPauseAtEndOfWindowInternal(msg.arg1 != 0);
break;
case MSG_RELEASE:
releaseInternal();
@@ -448,7 +467,7 @@
}
maybeNotifyPlaybackInfoChanged();
} catch (ExoPlaybackException e) {
- Log.e(TAG, getExoPlaybackExceptionMessage(e), e);
+ Log.e(TAG, "Playback error", e);
stopInternal(
/* forceResetRenderers= */ true,
/* resetPositionAndState= */ false,
@@ -456,19 +475,20 @@
playbackInfo = playbackInfo.copyWithPlaybackError(e);
maybeNotifyPlaybackInfoChanged();
} catch (IOException e) {
- Log.e(TAG, "Source error.", e);
+ ExoPlaybackException error = ExoPlaybackException.createForSource(e);
+ Log.e(TAG, "Playback error", error);
stopInternal(
/* forceResetRenderers= */ false,
/* resetPositionAndState= */ false,
/* acknowledgeStop= */ false);
- playbackInfo = playbackInfo.copyWithPlaybackError(ExoPlaybackException.createForSource(e));
+ playbackInfo = playbackInfo.copyWithPlaybackError(error);
maybeNotifyPlaybackInfoChanged();
} catch (RuntimeException | OutOfMemoryError e) {
- Log.e(TAG, "Internal runtime error.", e);
ExoPlaybackException error =
e instanceof OutOfMemoryError
? ExoPlaybackException.createForOutOfMemoryError((OutOfMemoryError) e)
: ExoPlaybackException.createForUnexpected((RuntimeException) e);
+ Log.e(TAG, "Playback error", error);
stopInternal(
/* forceResetRenderers= */ true,
/* resetPositionAndState= */ false,
@@ -481,20 +501,6 @@
// Private methods.
- private String getExoPlaybackExceptionMessage(ExoPlaybackException e) {
- if (e.type != ExoPlaybackException.TYPE_RENDERER) {
- return "Playback error.";
- }
- return "Renderer error: index="
- + e.rendererIndex
- + ", type="
- + Util.getTrackTypeString(renderers[e.rendererIndex].getTrackType())
- + ", format="
- + e.rendererFormat
- + ", rendererSupport="
- + RendererCapabilities.getFormatSupportString(e.rendererFormatSupport);
- }
-
/**
* Blocks the current thread until {@link #releaseInternal()} is executed on the playback Thread.
*
@@ -559,17 +565,10 @@
}
private void maybeNotifyPlaybackInfoChanged() {
- if (playbackInfoUpdate.hasPendingUpdate(playbackInfo)) {
- eventHandler
- .obtainMessage(
- MSG_PLAYBACK_INFO_CHANGED,
- playbackInfoUpdate.operationAcks,
- playbackInfoUpdate.positionDiscontinuity
- ? playbackInfoUpdate.discontinuityReason
- : C.INDEX_UNSET,
- playbackInfo)
- .sendToTarget();
- playbackInfoUpdate.reset(playbackInfo);
+ playbackInfoUpdate.setPlaybackInfo(playbackInfo);
+ if (playbackInfoUpdate.hasPendingChange) {
+ eventHandler.obtainMessage(MSG_PLAYBACK_INFO_CHANGED, playbackInfoUpdate).sendToTarget();
+ playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
}
}
@@ -578,76 +577,85 @@
resetInternal(
/* resetRenderers= */ false,
/* resetPosition= */ false,
- /* releasePlaylist= */ false,
- /* clearPlaylist= */ false,
+ /* releaseMediaSourceList= */ false,
+ /* clearMediaSourceList= */ false,
/* resetError= */ true);
loadControl.onPrepared();
setState(playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING);
- playlist.prepare(bandwidthMeter.getTransferListener());
+ mediaSourceList.prepare(bandwidthMeter.getTransferListener());
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
- private void setMediaItemsInternal(PlaylistUpdateMessage playlistUpdateMessage)
+ private void setMediaItemsInternal(MediaSourceListUpdateMessage mediaSourceListUpdateMessage)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
- if (playlistUpdateMessage.windowIndex != C.INDEX_UNSET) {
+ if (mediaSourceListUpdateMessage.windowIndex != C.INDEX_UNSET) {
pendingInitialSeekPosition =
new SeekPosition(
- new Playlist.PlaylistTimeline(
- playlistUpdateMessage.mediaSourceHolders, playlistUpdateMessage.shuffleOrder),
- playlistUpdateMessage.windowIndex,
- playlistUpdateMessage.positionUs);
+ new MediaSourceList.PlaylistTimeline(
+ mediaSourceListUpdateMessage.mediaSourceHolders,
+ mediaSourceListUpdateMessage.shuffleOrder),
+ mediaSourceListUpdateMessage.windowIndex,
+ mediaSourceListUpdateMessage.positionUs);
}
Timeline timeline =
- playlist.setMediaSources(
- playlistUpdateMessage.mediaSourceHolders, playlistUpdateMessage.shuffleOrder);
- handlePlaylistInfoRefreshed(timeline);
+ mediaSourceList.setMediaSources(
+ mediaSourceListUpdateMessage.mediaSourceHolders,
+ mediaSourceListUpdateMessage.shuffleOrder);
+ handleMediaSourceListInfoRefreshed(timeline);
}
- private void addMediaItemsInternal(PlaylistUpdateMessage addMessage, int insertionIndex)
+ private void addMediaItemsInternal(MediaSourceListUpdateMessage addMessage, int insertionIndex)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
Timeline timeline =
- playlist.addMediaSources(
- insertionIndex == C.INDEX_UNSET ? playlist.getSize() : insertionIndex,
+ mediaSourceList.addMediaSources(
+ insertionIndex == C.INDEX_UNSET ? mediaSourceList.getSize() : insertionIndex,
addMessage.mediaSourceHolders,
addMessage.shuffleOrder);
- handlePlaylistInfoRefreshed(timeline);
+ handleMediaSourceListInfoRefreshed(timeline);
}
private void moveMediaItemsInternal(MoveMediaItemsMessage moveMediaItemsMessage)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
Timeline timeline =
- playlist.moveMediaSourceRange(
+ mediaSourceList.moveMediaSourceRange(
moveMediaItemsMessage.fromIndex,
moveMediaItemsMessage.toIndex,
moveMediaItemsMessage.newFromIndex,
moveMediaItemsMessage.shuffleOrder);
- handlePlaylistInfoRefreshed(timeline);
+ handleMediaSourceListInfoRefreshed(timeline);
}
private void removeMediaItemsInternal(int fromIndex, int toIndex, ShuffleOrder shuffleOrder)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
- Timeline timeline = playlist.removeMediaSourceRange(fromIndex, toIndex, shuffleOrder);
- handlePlaylistInfoRefreshed(timeline);
+ Timeline timeline = mediaSourceList.removeMediaSourceRange(fromIndex, toIndex, shuffleOrder);
+ handleMediaSourceListInfoRefreshed(timeline);
}
- private void playlistUpdateRequestedInternal() throws ExoPlaybackException {
- handlePlaylistInfoRefreshed(playlist.createTimeline());
+ private void mediaSourceListUpdateRequestedInternal() throws ExoPlaybackException {
+ handleMediaSourceListInfoRefreshed(mediaSourceList.createTimeline());
}
private void setShuffleOrderInternal(ShuffleOrder shuffleOrder) throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
- Timeline timeline = playlist.setShuffleOrder(shuffleOrder);
- handlePlaylistInfoRefreshed(timeline);
+ Timeline timeline = mediaSourceList.setShuffleOrder(shuffleOrder);
+ handleMediaSourceListInfoRefreshed(timeline);
}
- private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException {
+ private void setPlayWhenReadyInternal(
+ boolean playWhenReady,
+ @PlaybackSuppressionReason int playbackSuppressionReason,
+ boolean operationAck,
+ @Player.PlayWhenReadyChangeReason int reason)
+ throws ExoPlaybackException {
+ playbackInfoUpdate.incrementPendingOperationAcks(operationAck ? 1 : 0);
+ playbackInfoUpdate.setPlayWhenReadyChangeReason(reason);
+ playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
rebuffering = false;
- this.playWhenReady = playWhenReady;
- if (!playWhenReady) {
+ if (!shouldPlayWhenReady()) {
stopRenderers();
updatePlaybackPositions();
} else {
@@ -660,6 +668,16 @@
}
}
+ private void setPauseAtEndOfWindowInternal(boolean pauseAtEndOfWindow)
+ throws ExoPlaybackException {
+ this.pauseAtEndOfWindow = pauseAtEndOfWindow;
+ if (queue.getReadingPeriod() != queue.getPlayingPeriod()) {
+ seekToCurrentPosition(/* sendDiscontinuity= */ true);
+ }
+ resetPendingPauseAtEndOfPeriod();
+ handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
+ }
+
private void setRepeatModeInternal(@Player.RepeatMode int repeatMode)
throws ExoPlaybackException {
this.repeatMode = repeatMode;
@@ -689,7 +707,9 @@
/* forceDisableRenderers= */ true,
/* forceBufferingState= */ false);
if (newPositionUs != playbackInfo.positionUs) {
- playbackInfo = copyWithNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
+ playbackInfo =
+ handlePositionDiscontinuity(
+ periodId, newPositionUs, playbackInfo.requestedContentPositionUs);
if (sendDiscontinuity) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
@@ -699,15 +719,19 @@
private void startRenderers() throws ExoPlaybackException {
rebuffering = false;
mediaClock.start();
- for (Renderer renderer : enabledRenderers) {
- renderer.start();
+ for (Renderer renderer : renderers) {
+ if (isRendererEnabled(renderer)) {
+ renderer.start();
+ }
}
}
private void stopRenderers() throws ExoPlaybackException {
mediaClock.stop();
- for (Renderer renderer : enabledRenderers) {
- ensureStopped(renderer);
+ for (Renderer renderer : renderers) {
+ if (isRendererEnabled(renderer)) {
+ ensureStopped(renderer);
+ }
}
}
@@ -728,8 +752,10 @@
// renderers are flushed. Only report the discontinuity externally if the position changed.
if (discontinuityPositionUs != playbackInfo.positionUs) {
playbackInfo =
- copyWithNewPosition(
- playbackInfo.periodId, discontinuityPositionUs, playbackInfo.contentPositionUs);
+ handlePositionDiscontinuity(
+ playbackInfo.periodId,
+ discontinuityPositionUs,
+ playbackInfo.requestedContentPositionUs);
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
} else {
@@ -777,7 +803,7 @@
playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
- if (renderer.getState() == Renderer.STATE_DISABLED) {
+ if (!isRendererEnabled(renderer)) {
continue;
}
// TODO: Each renderer should return the maximum delay before which it wishes to be called
@@ -807,31 +833,44 @@
}
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
- if (renderersEnded
- && playingPeriodHolder.prepared
- && (playingPeriodDurationUs == C.TIME_UNSET
- || playingPeriodDurationUs <= playbackInfo.positionUs)
- && playingPeriodHolder.info.isFinal) {
+ boolean finishedRendering =
+ renderersEnded
+ && playingPeriodHolder.prepared
+ && (playingPeriodDurationUs == C.TIME_UNSET
+ || playingPeriodDurationUs <= playbackInfo.positionUs);
+ if (finishedRendering && pendingPauseAtEndOfPeriod) {
+ pendingPauseAtEndOfPeriod = false;
+ setPlayWhenReadyInternal(
+ /* playWhenReady= */ false,
+ playbackInfo.playbackSuppressionReason,
+ /* operationAck= */ false,
+ Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM);
+ }
+ if (finishedRendering && playingPeriodHolder.info.isFinal) {
setState(Player.STATE_ENDED);
stopRenderers();
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING
&& shouldTransitionToReadyState(renderersAllowPlayback)) {
setState(Player.STATE_READY);
- if (playWhenReady) {
+ if (shouldPlayWhenReady()) {
startRenderers();
}
} else if (playbackInfo.playbackState == Player.STATE_READY
- && !(enabledRenderers.length == 0 ? isTimelineReady() : renderersAllowPlayback)) {
- rebuffering = playWhenReady;
+ && !(enabledRendererCount == 0 ? isTimelineReady() : renderersAllowPlayback)) {
+ rebuffering = shouldPlayWhenReady();
setState(Player.STATE_BUFFERING);
stopRenderers();
}
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
- for (Renderer renderer : enabledRenderers) {
- renderer.maybeThrowStreamError();
+ for (int i = 0; i < renderers.length; i++) {
+ if (isRendererEnabled(renderers[i])
+ && renderers[i].getStream() == playingPeriodHolder.sampleStreams[i]) {
+ renderers[i].maybeThrowStreamError();
+ }
}
- if (!shouldContinueLoading
+ if (throwWhenStuckBuffering
+ && !shouldContinueLoading
&& playbackInfo.totalBufferedDurationUs < 500_000
&& isLoadingPossible()) {
// Throw if the LoadControl prevents loading even if the buffer is empty or almost empty. We
@@ -841,10 +880,10 @@
}
}
- if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
+ if ((shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY)
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
- } else if (enabledRenderers.length != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
+ } else if (enabledRendererCount != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
} else {
handler.removeMessages(MSG_DO_SOME_WORK);
@@ -863,7 +902,7 @@
MediaPeriodId periodId;
long periodPositionUs;
- long contentPositionUs;
+ long requestedContentPosition;
boolean seekPositionAdjusted;
@Nullable
Pair<Object, Long> resolvedSeekPosition =
@@ -878,17 +917,21 @@
if (resolvedSeekPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed or is not ready and a suitable seek position could not be resolved.
- Pair<MediaPeriodId, Long> firstPeriodAndPosition = getDummyFirstMediaPeriodPosition();
+ Pair<MediaPeriodId, Long> firstPeriodAndPosition =
+ getDummyFirstMediaPeriodPosition(playbackInfo.timeline);
periodId = firstPeriodAndPosition.first;
periodPositionUs = firstPeriodAndPosition.second;
- contentPositionUs = C.TIME_UNSET;
- seekPositionAdjusted = true;
+ requestedContentPosition = C.TIME_UNSET;
+ seekPositionAdjusted = !playbackInfo.timeline.isEmpty();
} else {
// Update the resolved seek position to take ads into account.
Object periodUid = resolvedSeekPosition.first;
- contentPositionUs = resolvedSeekPosition.second;
+ long resolvedContentPosition = resolvedSeekPosition.second;
+ requestedContentPosition =
+ seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPosition;
periodId =
- queue.resolveMediaPeriodIdForAds(playbackInfo.timeline, periodUid, contentPositionUs);
+ queue.resolveMediaPeriodIdForAds(
+ playbackInfo.timeline, periodUid, resolvedContentPosition);
if (periodId.isAd()) {
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
periodPositionUs =
@@ -897,23 +940,25 @@
: 0;
seekPositionAdjusted = true;
} else {
- periodPositionUs = resolvedSeekPosition.second;
+ periodPositionUs = resolvedContentPosition;
seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
}
}
try {
- if (playbackInfo.timeline.isEmpty() || !playlist.isPrepared()) {
+ if (playbackInfo.timeline.isEmpty()) {
// Save seek position for later, as we are still waiting for a prepared source.
pendingInitialSeekPosition = seekPosition;
} else if (resolvedSeekPosition == null) {
// End playback, as we didn't manage to find a valid seek position.
- setState(Player.STATE_ENDED);
+ if (playbackInfo.playbackState != Player.STATE_IDLE) {
+ setState(Player.STATE_ENDED);
+ }
resetInternal(
/* resetRenderers= */ false,
/* resetPosition= */ true,
- /* releasePlaylist= */ false,
- /* clearPlaylist= */ false,
+ /* releaseMediaSourceList= */ false,
+ /* clearMediaSourceList= */ false,
/* resetError= */ true);
} else {
// Execute the seek in the current media periods.
@@ -927,7 +972,9 @@
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
newPeriodPositionUs, seekParameters);
}
- if (C.usToMs(newPeriodPositionUs) == C.usToMs(playbackInfo.positionUs)) {
+ if (C.usToMs(newPeriodPositionUs) == C.usToMs(playbackInfo.positionUs)
+ && (playbackInfo.playbackState == Player.STATE_BUFFERING
+ || playbackInfo.playbackState == Player.STATE_READY)) {
// Seek will be performed to the current position. Do nothing.
periodPositionUs = playbackInfo.positionUs;
return;
@@ -942,7 +989,8 @@
periodPositionUs = newPeriodPositionUs;
}
} finally {
- playbackInfo = copyWithNewPosition(periodId, periodPositionUs, contentPositionUs);
+ playbackInfo =
+ handlePositionDiscontinuity(periodId, periodPositionUs, requestedContentPosition);
if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
}
@@ -972,11 +1020,11 @@
setState(Player.STATE_BUFFERING);
}
- // Find the requested period if it's already prepared.
+ // Find the requested period if it already exists.
@Nullable MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
@Nullable MediaPeriodHolder newPlayingPeriodHolder = oldPlayingPeriodHolder;
while (newPlayingPeriodHolder != null) {
- if (periodId.equals(newPlayingPeriodHolder.info.id) && newPlayingPeriodHolder.prepared) {
+ if (periodId.equals(newPlayingPeriodHolder.info.id)) {
break;
}
newPlayingPeriodHolder = newPlayingPeriodHolder.getNext();
@@ -988,27 +1036,37 @@
|| oldPlayingPeriodHolder != newPlayingPeriodHolder
|| (newPlayingPeriodHolder != null
&& newPlayingPeriodHolder.toRendererTime(periodPositionUs) < 0)) {
- for (Renderer renderer : enabledRenderers) {
+ for (Renderer renderer : renderers) {
disableRenderer(renderer);
}
- enabledRenderers = new Renderer[0];
if (newPlayingPeriodHolder != null) {
// Update the queue and reenable renderers if the requested media period already exists.
while (queue.getPlayingPeriod() != newPlayingPeriodHolder) {
queue.advancePlayingPeriod();
}
+ queue.removeAfter(newPlayingPeriodHolder);
newPlayingPeriodHolder.setRendererOffset(/* rendererPositionOffsetUs= */ 0);
- enablePlayingPeriodRenderers();
+ enableRenderers();
}
}
// Do the actual seeking.
if (newPlayingPeriodHolder != null) {
queue.removeAfter(newPlayingPeriodHolder);
- if (newPlayingPeriodHolder.hasEnabledTracks) {
- periodPositionUs = newPlayingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
- newPlayingPeriodHolder.mediaPeriod.discardBuffer(
- periodPositionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
+ if (!newPlayingPeriodHolder.prepared) {
+ newPlayingPeriodHolder.info =
+ newPlayingPeriodHolder.info.copyWithStartPositionUs(periodPositionUs);
+ } else {
+ if (newPlayingPeriodHolder.info.durationUs != C.TIME_UNSET
+ && periodPositionUs >= newPlayingPeriodHolder.info.durationUs) {
+ // Make sure seek position doesn't exceed period duration.
+ periodPositionUs = Math.max(0, newPlayingPeriodHolder.info.durationUs - 1);
+ }
+ if (newPlayingPeriodHolder.hasEnabledTracks) {
+ periodPositionUs = newPlayingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
+ newPlayingPeriodHolder.mediaPeriod.discardBuffer(
+ periodPositionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
+ }
}
resetRendererPosition(periodPositionUs);
maybeContinueLoading();
@@ -1030,16 +1088,17 @@
? periodPositionUs
: playingMediaPeriod.toRendererTime(periodPositionUs);
mediaClock.resetPosition(rendererPositionUs);
- for (Renderer renderer : enabledRenderers) {
- renderer.resetPosition(rendererPositionUs);
+ for (Renderer renderer : renderers) {
+ if (isRendererEnabled(renderer)) {
+ renderer.resetPosition(rendererPositionUs);
+ }
}
notifyTrackSelectionDiscontinuity();
}
- private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
- mediaClock.setPlaybackParameters(playbackParameters);
- sendPlaybackParametersChangedInternal(
- mediaClock.getPlaybackParameters(), /* acknowledgeCommand= */ true);
+ private void setPlaybackSpeedInternal(float playbackSpeed) {
+ mediaClock.setPlaybackSpeed(playbackSpeed);
+ sendPlaybackSpeedChangedInternal(mediaClock.getPlaybackSpeed(), /* acknowledgeCommand= */ true);
}
private void setSeekParametersInternal(SeekParameters seekParameters) {
@@ -1052,7 +1111,7 @@
this.foregroundMode = foregroundMode;
if (!foregroundMode) {
for (Renderer renderer : renderers) {
- if (renderer.getState() == Renderer.STATE_DISABLED) {
+ if (!isRendererEnabled(renderer)) {
renderer.reset();
}
}
@@ -1071,8 +1130,8 @@
resetInternal(
/* resetRenderers= */ forceResetRenderers || !foregroundMode,
/* resetPosition= */ resetPositionAndState,
- /* releasePlaylist= */ true,
- /* clearPlaylist= */ resetPositionAndState,
+ /* releaseMediaSourceList= */ true,
+ /* clearMediaSourceList= */ resetPositionAndState,
/* resetError= */ resetPositionAndState);
playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeStop ? 1 : 0);
loadControl.onStopped();
@@ -1083,8 +1142,8 @@
resetInternal(
/* resetRenderers= */ true,
/* resetPosition= */ true,
- /* releasePlaylist= */ true,
- /* clearPlaylist= */ true,
+ /* releaseMediaSourceList= */ true,
+ /* clearMediaSourceList= */ true,
/* resetError= */ false);
loadControl.onReleased();
setState(Player.STATE_IDLE);
@@ -1098,14 +1157,14 @@
private void resetInternal(
boolean resetRenderers,
boolean resetPosition,
- boolean releasePlaylist,
- boolean clearPlaylist,
+ boolean releaseMediaSourceList,
+ boolean clearMediaSourceList,
boolean resetError) {
handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false;
mediaClock.stop();
rendererPositionUs = 0;
- for (Renderer renderer : enabledRenderers) {
+ for (Renderer renderer : renderers) {
try {
disableRenderer(renderer);
} catch (ExoPlaybackException | RuntimeException e) {
@@ -1123,67 +1182,75 @@
}
}
}
- enabledRenderers = new Renderer[0];
+ enabledRendererCount = 0;
- queue.clear();
- shouldContinueLoading = false;
Timeline timeline = playbackInfo.timeline;
- if (clearPlaylist) {
- timeline = playlist.clear(/* shuffleOrder= */ null);
+ if (clearMediaSourceList) {
+ timeline = mediaSourceList.clear(/* shuffleOrder= */ null);
for (PendingMessageInfo pendingMessageInfo : pendingMessages) {
pendingMessageInfo.message.markAsProcessed(/* isDelivered= */ false);
}
pendingMessages.clear();
nextPendingMessageIndex = 0;
+ resetPosition = true;
}
MediaPeriodId mediaPeriodId = playbackInfo.periodId;
long startPositionUs = playbackInfo.positionUs;
- long contentPositionUs = playbackInfo.contentPositionUs;
- boolean resetTrackInfo = clearPlaylist;
+ long requestedContentPositionUs =
+ shouldUseRequestedContentPosition(playbackInfo, period, window)
+ ? playbackInfo.requestedContentPositionUs
+ : playbackInfo.positionUs;
+ boolean resetTrackInfo = clearMediaSourceList;
if (resetPosition) {
pendingInitialSeekPosition = null;
- Pair<MediaPeriodId, Long> firstPeriodAndPosition = getDummyFirstMediaPeriodPosition();
+ Pair<MediaPeriodId, Long> firstPeriodAndPosition = getDummyFirstMediaPeriodPosition(timeline);
mediaPeriodId = firstPeriodAndPosition.first;
startPositionUs = firstPeriodAndPosition.second;
- contentPositionUs = C.TIME_UNSET;
+ requestedContentPositionUs = C.TIME_UNSET;
if (!mediaPeriodId.equals(playbackInfo.periodId)) {
resetTrackInfo = true;
}
}
+
+ queue.clear();
+ shouldContinueLoading = false;
+
playbackInfo =
new PlaybackInfo(
timeline,
mediaPeriodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackInfo.playbackState,
resetError ? null : playbackInfo.playbackError,
/* isLoading= */ false,
resetTrackInfo ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetTrackInfo ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
mediaPeriodId,
+ playbackInfo.playWhenReady,
+ playbackInfo.playbackSuppressionReason,
startPositionUs,
/* totalBufferedDurationUs= */ 0,
startPositionUs);
- if (releasePlaylist) {
- playlist.release();
+ if (releaseMediaSourceList) {
+ mediaSourceList.release();
}
}
- private Pair<MediaPeriodId, Long> getDummyFirstMediaPeriodPosition() {
- if (playbackInfo.timeline.isEmpty()) {
+ private Pair<MediaPeriodId, Long> getDummyFirstMediaPeriodPosition(Timeline timeline) {
+ if (timeline.isEmpty()) {
return Pair.create(PlaybackInfo.getDummyPeriodForEmptyTimeline(), 0L);
}
- int firstWindowIndex = playbackInfo.timeline.getFirstWindowIndex(shuffleModeEnabled);
+ int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
Pair<Object, Long> firstPeriodAndPosition =
- playbackInfo.timeline.getPeriodPosition(
+ timeline.getPeriodPosition(
window, period, firstWindowIndex, /* windowPositionUs= */ C.TIME_UNSET);
// Add ad metadata if any and propagate the window sequence number to new period id.
MediaPeriodId firstPeriodId =
queue.resolveMediaPeriodIdForAds(
- playbackInfo.timeline, firstPeriodAndPosition.first, /* positionUs= */ 0);
+ timeline, firstPeriodAndPosition.first, /* positionUs= */ 0);
long positionUs = firstPeriodAndPosition.second;
if (firstPeriodId.isAd()) {
- playbackInfo.timeline.getPeriodByUid(firstPeriodId.periodUid, period);
+ timeline.getPeriodByUid(firstPeriodId.periodUid, period);
positionUs =
firstPeriodId.adIndexInAdGroup == period.getFirstAdIndexToPlay(firstPeriodId.adGroupIndex)
? period.getAdResumePositionUs()
@@ -1201,7 +1268,14 @@
pendingMessages.add(new PendingMessageInfo(message));
} else {
PendingMessageInfo pendingMessageInfo = new PendingMessageInfo(message);
- if (resolvePendingMessagePosition(pendingMessageInfo)) {
+ if (resolvePendingMessagePosition(
+ pendingMessageInfo,
+ /* newTimeline= */ playbackInfo.timeline,
+ /* previousTimeline= */ playbackInfo.timeline,
+ repeatMode,
+ shuffleModeEnabled,
+ window,
+ period)) {
pendingMessages.add(pendingMessageInfo);
// Ensure new message is inserted according to playback order.
Collections.sort(pendingMessages);
@@ -1253,9 +1327,20 @@
}
}
- private void resolvePendingMessagePositions() {
+ private void resolvePendingMessagePositions(Timeline newTimeline, Timeline previousTimeline) {
+ if (newTimeline.isEmpty() && previousTimeline.isEmpty()) {
+ // Keep all messages unresolved until we have a non-empty timeline.
+ return;
+ }
for (int i = pendingMessages.size() - 1; i >= 0; i--) {
- if (!resolvePendingMessagePosition(pendingMessages.get(i))) {
+ if (!resolvePendingMessagePosition(
+ pendingMessages.get(i),
+ newTimeline,
+ previousTimeline,
+ repeatMode,
+ shuffleModeEnabled,
+ window,
+ period)) {
// Unable to resolve a new position for the message. Remove it.
pendingMessages.get(i).message.markAsProcessed(/* isDelivered= */ false);
pendingMessages.remove(i);
@@ -1265,40 +1350,6 @@
Collections.sort(pendingMessages);
}
- private boolean resolvePendingMessagePosition(PendingMessageInfo pendingMessageInfo) {
- if (pendingMessageInfo.resolvedPeriodUid == null) {
- // Position is still unresolved. Try to find window in current timeline.
- @Nullable
- Pair<Object, Long> periodPosition =
- resolveSeekPosition(
- playbackInfo.timeline,
- new SeekPosition(
- pendingMessageInfo.message.getTimeline(),
- pendingMessageInfo.message.getWindowIndex(),
- C.msToUs(pendingMessageInfo.message.getPositionMs())),
- /* trySubsequentPeriods= */ false,
- repeatMode,
- shuffleModeEnabled,
- window,
- period);
- if (periodPosition == null) {
- return false;
- }
- pendingMessageInfo.setResolvedPosition(
- playbackInfo.timeline.getIndexOfPeriod(periodPosition.first),
- periodPosition.second,
- periodPosition.first);
- } else {
- // Position has been resolved for a previous timeline. Try to find the updated period index.
- int index = playbackInfo.timeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid);
- if (index == C.INDEX_UNSET) {
- return false;
- }
- pendingMessageInfo.resolvedPeriodIndex = index;
- }
- return true;
- }
-
private void maybeTriggerPendingMessages(long oldPeriodPositionUs, long newPeriodPositionUs)
throws ExoPlaybackException {
if (pendingMessages.isEmpty() || playbackInfo.periodId.isAd()) {
@@ -1368,13 +1419,17 @@
}
private void disableRenderer(Renderer renderer) throws ExoPlaybackException {
+ if (!isRendererEnabled(renderer)) {
+ return;
+ }
mediaClock.onRendererDisabled(renderer);
ensureStopped(renderer);
renderer.disable();
+ enabledRendererCount--;
}
private void reselectTracksInternal() throws ExoPlaybackException {
- float playbackSpeed = mediaClock.getPlaybackParameters().speed;
+ float playbackSpeed = mediaClock.getPlaybackSpeed();
// Reselect tracks on each period in turn, until the selection changes.
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
@@ -1407,23 +1462,19 @@
playingPeriodHolder.applyTrackSelection(
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags);
playbackInfo =
- copyWithNewPosition(
- playbackInfo.periodId, periodPositionUs, playbackInfo.contentPositionUs);
+ handlePositionDiscontinuity(
+ playbackInfo.periodId, periodPositionUs, playbackInfo.requestedContentPositionUs);
if (playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
resetRendererPosition(periodPositionUs);
}
- int enabledRendererCount = 0;
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
- rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
+ rendererWasEnabledFlags[i] = isRendererEnabled(renderer);
SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
- if (sampleStream != null) {
- enabledRendererCount++;
- }
if (rendererWasEnabledFlags[i]) {
if (sampleStream != renderer.getStream()) {
// We need to disable the renderer.
@@ -1434,7 +1485,7 @@
}
}
}
- enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
+ enableRenderers(rendererWasEnabledFlags);
} else {
// Release and re-prepare/buffer periods after the one whose selection changed.
queue.removeAfter(periodHolder);
@@ -1480,7 +1531,7 @@
}
private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
- if (enabledRenderers.length == 0) {
+ if (enabledRendererCount == 0) {
// If there are no enabled renderers, determine whether we're ready based on the timeline.
return isTimelineReady();
}
@@ -1497,7 +1548,7 @@
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
|| loadControl.shouldStartPlayback(
- getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
+ getTotalBufferedDurationUs(), mediaClock.getPlaybackSpeed(), rebuffering);
}
private boolean isTimelineReady() {
@@ -1505,23 +1556,24 @@
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
return playingPeriodHolder.prepared
&& (playingPeriodDurationUs == C.TIME_UNSET
- || playbackInfo.positionUs < playingPeriodDurationUs);
+ || playbackInfo.positionUs < playingPeriodDurationUs
+ || !shouldPlayWhenReady());
}
private void maybeThrowSourceInfoRefreshError() throws IOException {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder != null) {
// Defer throwing until we read all available media periods.
- for (Renderer renderer : enabledRenderers) {
- if (!renderer.hasReadStreamToEnd()) {
+ for (Renderer renderer : renderers) {
+ if (isRendererEnabled(renderer) && !renderer.hasReadStreamToEnd()) {
return;
}
}
}
- playlist.maybeThrowSourceInfoRefreshError();
+ mediaSourceList.maybeThrowSourceInfoRefreshError();
}
- private void handlePlaylistInfoRefreshed(Timeline timeline) throws ExoPlaybackException {
+ private void handleMediaSourceListInfoRefreshed(Timeline timeline) throws ExoPlaybackException {
PositionUpdateForPlaylistChange positionUpdate =
resolvePositionForPlaylistChange(
timeline,
@@ -1533,11 +1585,11 @@
window,
period);
MediaPeriodId newPeriodId = positionUpdate.periodId;
- long newContentPositionUs = positionUpdate.contentPositionUs;
+ long newRequestedContentPositionUs = positionUpdate.requestedContentPositionUs;
boolean forceBufferingState = positionUpdate.forceBufferingState;
long newPositionUs = positionUpdate.periodPositionUs;
- boolean isPlaybackPositionUnchanged =
- playbackInfo.periodId.equals(newPeriodId) && newPositionUs == playbackInfo.positionUs;
+ boolean periodPositionChanged =
+ !playbackInfo.periodId.equals(newPeriodId) || newPositionUs != playbackInfo.positionUs;
try {
if (positionUpdate.endPlayback) {
@@ -1547,11 +1599,11 @@
resetInternal(
/* resetRenderers= */ false,
/* resetPosition= */ false,
- /* releasePlaylist= */ false,
- /* clearPlaylist= */ false,
+ /* releaseMediaSourceList= */ false,
+ /* clearMediaSourceList= */ false,
/* resetError= */ true);
}
- if (isPlaybackPositionUnchanged) {
+ if (!periodPositionChanged) {
// We can keep the current playing period. Update the rest of the queued periods.
if (!queue.updateQueuedPeriods(
timeline, rendererPositionUs, getMaxRendererReadPositionUs())) {
@@ -1560,23 +1612,25 @@
} else if (!timeline.isEmpty()) {
// Something changed. Seek to new start position.
@Nullable MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
- if (periodHolder != null) {
+ while (periodHolder != null) {
// Update the new playing media period info if it already exists.
- while (periodHolder.getNext() != null) {
- periodHolder = periodHolder.getNext();
- if (periodHolder.info.id.equals(newPeriodId)) {
- periodHolder.info = queue.getUpdatedMediaPeriodInfo(timeline, periodHolder.info);
- }
+ if (periodHolder.info.id.equals(newPeriodId)) {
+ periodHolder.info = queue.getUpdatedMediaPeriodInfo(timeline, periodHolder.info);
}
+ periodHolder = periodHolder.getNext();
}
newPositionUs = seekToPeriodPosition(newPeriodId, newPositionUs, forceBufferingState);
}
} finally {
- if (!isPlaybackPositionUnchanged) {
- playbackInfo = copyWithNewPosition(newPeriodId, newPositionUs, newContentPositionUs);
+ if (periodPositionChanged
+ || newRequestedContentPositionUs != playbackInfo.requestedContentPositionUs) {
+ playbackInfo =
+ handlePositionDiscontinuity(newPeriodId, newPositionUs, newRequestedContentPositionUs);
}
+ resetPendingPauseAtEndOfPeriod();
+ resolvePendingMessagePositions(
+ /* newTimeline= */ timeline, /* previousTimeline= */ playbackInfo.timeline);
playbackInfo = playbackInfo.copyWithTimeline(timeline);
- resolvePendingMessagePositions();
if (!timeline.isEmpty()) {
// Retain pending seek position only while the timeline is still empty.
pendingInitialSeekPosition = null;
@@ -1595,7 +1649,7 @@
return maxReadPositionUs;
}
for (int i = 0; i < renderers.length; i++) {
- if (renderers[i].getState() == Renderer.STATE_DISABLED
+ if (!isRendererEnabled(renderers[i])
|| renderers[i].getStream() != readingHolder.sampleStreams[i]) {
// Ignore disabled renderers and renderers with sample streams from previous periods.
continue;
@@ -1611,13 +1665,14 @@
}
private void updatePeriods() throws ExoPlaybackException, IOException {
- if (playbackInfo.timeline.isEmpty() || !playlist.isPrepared()) {
+ if (playbackInfo.timeline.isEmpty() || !mediaSourceList.isPrepared()) {
// We're waiting to get information about periods.
- playlist.maybeThrowSourceInfoRefreshError();
+ mediaSourceList.maybeThrowSourceInfoRefreshError();
return;
}
maybeUpdateLoadingPeriod();
maybeUpdateReadingPeriod();
+ maybeUpdateReadingRenderers();
maybeUpdatePlayingPeriod();
}
@@ -1633,7 +1688,7 @@
rendererCapabilities,
trackSelector,
loadControl.getAllocator(),
- playlist,
+ mediaSourceList,
info,
emptyTrackSelectorResult);
mediaPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs);
@@ -1653,15 +1708,16 @@
}
}
- private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
+ private void maybeUpdateReadingPeriod() {
@Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (readingPeriodHolder == null) {
return;
}
- if (readingPeriodHolder.getNext() == null) {
- // We don't have a successor to advance the reading period to.
- if (readingPeriodHolder.info.isFinal) {
+ if (readingPeriodHolder.getNext() == null || pendingPauseAtEndOfPeriod) {
+ // We don't have a successor to advance the reading period to or we want to let them end
+ // intentionally to pause at the end of the period.
+ if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) {
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
@@ -1681,8 +1737,9 @@
return;
}
- if (!readingPeriodHolder.getNext().prepared) {
- // The successor is not prepared yet.
+ if (!readingPeriodHolder.getNext().prepared
+ && rendererPositionUs < readingPeriodHolder.getNext().getStartPositionRendererTime()) {
+ // The successor is not prepared yet and playback hasn't reached the transition point.
return;
}
@@ -1690,47 +1747,77 @@
readingPeriodHolder = queue.advanceReadingPeriod();
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
- if (readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET) {
+ if (readingPeriodHolder.prepared
+ && readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET) {
// The new period starts with a discontinuity, so the renderers will play out all data, then
// be disabled and re-enabled when they start playing the next period.
setAllRendererStreamsFinal();
return;
}
for (int i = 0; i < renderers.length; i++) {
- Renderer renderer = renderers[i];
- boolean rendererWasEnabled = oldTrackSelectorResult.isRendererEnabled(i);
- if (rendererWasEnabled && !renderer.isCurrentStreamFinal()) {
- // The renderer is enabled and its stream is not final, so we still have a chance to replace
- // the sample streams.
- TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
- boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(i);
+ boolean oldRendererEnabled = oldTrackSelectorResult.isRendererEnabled(i);
+ boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(i);
+ if (oldRendererEnabled && !renderers[i].isCurrentStreamFinal()) {
boolean isNoSampleRenderer = rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE;
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
- if (newRendererEnabled && newConfig.equals(oldConfig) && !isNoSampleRenderer) {
- // Replace the renderer's SampleStream so the transition to playing the next period can
- // be seamless.
- // This should be avoided for no-sample renderer, because skipping ahead for such
- // renderer doesn't have any benefit (the renderer does not consume the sample stream),
- // and it will change the provided rendererOffsetUs while the renderer is still
- // rendering from the playing media period.
- Format[] formats = getFormats(newSelection);
- renderer.replaceStream(
- formats,
- readingPeriodHolder.sampleStreams[i],
- readingPeriodHolder.getRendererOffset());
- } else {
+ if (!newRendererEnabled || !newConfig.equals(oldConfig) || isNoSampleRenderer) {
// The renderer will be disabled when transitioning to playing the next period, because
// there's no new selection, or because a configuration change is required, or because
// it's a no-sample renderer for which rendererOffsetUs should be updated only when
// starting to play the next period. Mark the SampleStream as final to play out any
// remaining data.
- renderer.setCurrentStreamFinal();
+ renderers[i].setCurrentStreamFinal();
}
}
}
}
+ private void maybeUpdateReadingRenderers() throws ExoPlaybackException {
+ @Nullable MediaPeriodHolder readingPeriod = queue.getReadingPeriod();
+ if (readingPeriod == null
+ || queue.getPlayingPeriod() == readingPeriod
+ || readingPeriod.allRenderersEnabled) {
+ // Not reading ahead or all renderers updated.
+ return;
+ }
+ if (replaceStreamsOrDisableRendererForTransition()) {
+ enableRenderers();
+ }
+ }
+
+ private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException {
+ MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
+ TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
+ boolean needsToWaitForRendererToEnd = false;
+ for (int i = 0; i < renderers.length; i++) {
+ Renderer renderer = renderers[i];
+ if (!isRendererEnabled(renderer)) {
+ continue;
+ }
+ boolean rendererIsReadingOldStream =
+ renderer.getStream() != readingPeriodHolder.sampleStreams[i];
+ boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
+ if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
+ // All done.
+ continue;
+ }
+ if (!renderer.isCurrentStreamFinal()) {
+ // The renderer stream is not final, so we can replace the sample streams immediately.
+ Format[] formats = getFormats(newTrackSelectorResult.selections.get(i));
+ renderer.replaceStream(
+ formats, readingPeriodHolder.sampleStreams[i], readingPeriodHolder.getRendererOffset());
+ } else if (renderer.isEnded()) {
+ // The renderer has finished playback, so we can disable it now.
+ disableRenderer(renderer);
+ } else {
+ // We need to wait until rendering finished before disabling the renderer.
+ needsToWaitForRendererToEnd = true;
+ }
+ }
+ return !needsToWaitForRendererToEnd;
+ }
+
private void maybeUpdatePlayingPeriod() throws ExoPlaybackException {
boolean advancedPlayingPeriod = false;
while (shouldAdvancePlayingPeriod()) {
@@ -1739,32 +1826,34 @@
maybeNotifyPlaybackInfoChanged();
}
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
- if (oldPlayingPeriodHolder == queue.getReadingPeriod()) {
- // The reading period hasn't advanced yet, so we can't seamlessly replace the SampleStreams
- // anymore and need to re-enable the renderers. Set all current streams final to do that.
- setAllRendererStreamsFinal();
- }
- boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
- disablePlayingPeriodRenderersForTransition(rendererWasEnabledFlags);
MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod();
playbackInfo =
- copyWithNewPosition(
+ handlePositionDiscontinuity(
newPlayingPeriodHolder.info.id,
newPlayingPeriodHolder.info.startPositionUs,
- newPlayingPeriodHolder.info.contentPositionUs);
+ newPlayingPeriodHolder.info.requestedContentPositionUs);
int discontinuityReason =
oldPlayingPeriodHolder.info.isLastInTimelinePeriod
? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
: Player.DISCONTINUITY_REASON_AD_INSERTION;
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
- enablePlayingPeriodRenderers(rendererWasEnabledFlags);
+ resetPendingPauseAtEndOfPeriod();
updatePlaybackPositions();
advancedPlayingPeriod = true;
}
}
+ private void resetPendingPauseAtEndOfPeriod() {
+ @Nullable MediaPeriodHolder playingPeriod = queue.getPlayingPeriod();
+ pendingPauseAtEndOfPeriod =
+ playingPeriod != null && playingPeriod.info.isLastInTimelineWindow && pauseAtEndOfWindow;
+ }
+
private boolean shouldAdvancePlayingPeriod() {
- if (!playWhenReady) {
+ if (!shouldPlayWhenReady()) {
+ return false;
+ }
+ if (pendingPauseAtEndOfPeriod) {
return false;
}
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
@@ -1772,14 +1861,9 @@
return false;
}
MediaPeriodHolder nextPlayingPeriodHolder = playingPeriodHolder.getNext();
- if (nextPlayingPeriodHolder == null) {
- return false;
- }
- MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
- if (playingPeriodHolder == readingPeriodHolder && !hasReadingPeriodFinishedReading()) {
- return false;
- }
- return rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime();
+ return nextPlayingPeriodHolder != null
+ && rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime()
+ && nextPlayingPeriodHolder.allRenderersEnabled;
}
private boolean hasReadingPeriodFinishedReading() {
@@ -1813,17 +1897,18 @@
return;
}
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
- loadingPeriodHolder.handlePrepared(
- mediaClock.getPlaybackParameters().speed, playbackInfo.timeline);
+ loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackSpeed(), playbackInfo.timeline);
updateLoadControlTrackSelection(
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult());
if (loadingPeriodHolder == queue.getPlayingPeriod()) {
// This is the first prepared period, so update the position and the renderers.
resetRendererPosition(loadingPeriodHolder.info.startPositionUs);
- enablePlayingPeriodRenderers();
+ enableRenderers();
playbackInfo =
- copyWithNewPosition(
- playbackInfo.periodId, playbackInfo.positionUs, playbackInfo.contentPositionUs);
+ handlePositionDiscontinuity(
+ playbackInfo.periodId,
+ loadingPeriodHolder.info.startPositionUs,
+ playbackInfo.requestedContentPositionUs);
}
maybeContinueLoading();
}
@@ -1837,17 +1922,15 @@
maybeContinueLoading();
}
- private void handlePlaybackParameters(
- PlaybackParameters playbackParameters, boolean acknowledgeCommand)
+ private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand)
throws ExoPlaybackException {
eventHandler
- .obtainMessage(
- MSG_PLAYBACK_PARAMETERS_CHANGED, acknowledgeCommand ? 1 : 0, 0, playbackParameters)
+ .obtainMessage(MSG_PLAYBACK_SPEED_CHANGED, acknowledgeCommand ? 1 : 0, 0, playbackSpeed)
.sendToTarget();
- updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
+ updateTrackSelectionPlaybackSpeed(playbackSpeed);
for (Renderer renderer : renderers) {
if (renderer != null) {
- renderer.setOperatingRate(playbackParameters.speed);
+ renderer.setOperatingRate(playbackSpeed);
}
}
}
@@ -1864,10 +1947,16 @@
if (!isLoadingPossible()) {
return false;
}
+ MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
long bufferedDurationUs =
- getTotalBufferedDurationUs(queue.getLoadingPeriod().getNextLoadPositionUs());
- float playbackSpeed = mediaClock.getPlaybackParameters().speed;
- return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
+ getTotalBufferedDurationUs(loadingPeriodHolder.getNextLoadPositionUs());
+ long playbackPositionUs =
+ loadingPeriodHolder == queue.getPlayingPeriod()
+ ? loadingPeriodHolder.toPeriodTime(rendererPositionUs)
+ : loadingPeriodHolder.toPeriodTime(rendererPositionUs)
+ - loadingPeriodHolder.info.startPositionUs;
+ return loadControl.shouldContinueLoading(
+ playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackSpeed());
}
private boolean isLoadingPossible() {
@@ -1892,13 +1981,16 @@
}
@CheckResult
- private PlaybackInfo copyWithNewPosition(
+ private PlaybackInfo handlePositionDiscontinuity(
MediaPeriodId mediaPeriodId, long positionUs, long contentPositionUs) {
deliverPendingMessageAtStartPositionRequired =
- positionUs != playbackInfo.positionUs || !mediaPeriodId.equals(playbackInfo.periodId);
+ deliverPendingMessageAtStartPositionRequired
+ || positionUs != playbackInfo.positionUs
+ || !mediaPeriodId.equals(playbackInfo.periodId);
+ resetPendingPauseAtEndOfPeriod();
TrackGroupArray trackGroupArray = playbackInfo.trackGroups;
TrackSelectorResult trackSelectorResult = playbackInfo.trackSelectorResult;
- if (playlist.isPrepared()) {
+ if (mediaSourceList.isPrepared()) {
@Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
trackGroupArray =
playingPeriodHolder == null
@@ -1922,47 +2014,13 @@
trackSelectorResult);
}
- private void disablePlayingPeriodRenderersForTransition(boolean[] outRendererWasEnabledFlags)
- throws ExoPlaybackException {
- MediaPeriodHolder oldPlayingPeriodHolder = Assertions.checkNotNull(queue.getPlayingPeriod());
- MediaPeriodHolder newPlayingPeriodHolder =
- Assertions.checkNotNull(oldPlayingPeriodHolder.getNext());
- for (int i = 0; i < renderers.length; i++) {
- Renderer renderer = renderers[i];
- outRendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
- if (outRendererWasEnabledFlags[i]
- && (!newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)
- || (renderer.isCurrentStreamFinal()
- && renderer.getStream() == oldPlayingPeriodHolder.sampleStreams[i]))) {
- // The renderer should be disabled before playing the next period, either because it's not
- // needed to play the next period, or because we need to re-enable it as its current stream
- // is final and it's not reading ahead.
- disableRenderer(renderer);
- }
- }
+ private void enableRenderers() throws ExoPlaybackException {
+ enableRenderers(/* rendererWasEnabledFlags= */ new boolean[renderers.length]);
}
- private void enablePlayingPeriodRenderers() throws ExoPlaybackException {
- enablePlayingPeriodRenderers(/* rendererWasEnabledFlags= */ new boolean[renderers.length]);
- }
-
- private void enablePlayingPeriodRenderers(boolean[] rendererWasEnabledFlags)
- throws ExoPlaybackException {
- MediaPeriodHolder playingPeriodHolder = Assertions.checkNotNull(queue.getPlayingPeriod());
- int enabledRendererCount = 0;
- for (int i = 0; i < renderers.length; i++) {
- if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) {
- enabledRendererCount++;
- }
- }
- enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
- }
-
- private void enableRenderers(boolean[] rendererWasEnabledFlags, int totalEnabledRendererCount)
- throws ExoPlaybackException {
- enabledRenderers = new Renderer[totalEnabledRendererCount];
- int enabledRendererCount = 0;
- TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult();
+ private void enableRenderers(boolean[] rendererWasEnabledFlags) throws ExoPlaybackException {
+ MediaPeriodHolder readingMediaPeriod = queue.getReadingPeriod();
+ TrackSelectorResult trackSelectorResult = readingMediaPeriod.getTrackSelectorResult();
// Reset all disabled renderers before enabling any new ones. This makes sure resources released
// by the disabled renderers will be available to renderers that are being enabled.
for (int i = 0; i < renderers.length; i++) {
@@ -1973,40 +2031,43 @@
// Enable the renderers.
for (int i = 0; i < renderers.length; i++) {
if (trackSelectorResult.isRendererEnabled(i)) {
- enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++);
+ enableRenderer(i, rendererWasEnabledFlags[i]);
}
}
+ readingMediaPeriod.allRenderersEnabled = true;
}
- private void enableRenderer(
- int rendererIndex, boolean wasRendererEnabled, int enabledRendererIndex)
+ private void enableRenderer(int rendererIndex, boolean wasRendererEnabled)
throws ExoPlaybackException {
- MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
Renderer renderer = renderers[rendererIndex];
- enabledRenderers[enabledRendererIndex] = renderer;
- if (renderer.getState() == Renderer.STATE_DISABLED) {
- TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
- RendererConfiguration rendererConfiguration =
- trackSelectorResult.rendererConfigurations[rendererIndex];
- TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex);
- Format[] formats = getFormats(newSelection);
- // The renderer needs enabling with its new track selection.
- boolean playing = playWhenReady && playbackInfo.playbackState == Player.STATE_READY;
- // Consider as joining only if the renderer was previously disabled.
- boolean joining = !wasRendererEnabled && playing;
- // Enable the renderer.
- renderer.enable(
- rendererConfiguration,
- formats,
- playingPeriodHolder.sampleStreams[rendererIndex],
- rendererPositionUs,
- joining,
- playingPeriodHolder.getRendererOffset());
- mediaClock.onRendererEnabled(renderer);
- // Start the renderer if playing.
- if (playing) {
- renderer.start();
- }
+ if (isRendererEnabled(renderer)) {
+ return;
+ }
+ MediaPeriodHolder periodHolder = queue.getReadingPeriod();
+ boolean mayRenderStartOfStream = periodHolder == queue.getPlayingPeriod();
+ TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
+ RendererConfiguration rendererConfiguration =
+ trackSelectorResult.rendererConfigurations[rendererIndex];
+ TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex);
+ Format[] formats = getFormats(newSelection);
+ // The renderer needs enabling with its new track selection.
+ boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
+ // Consider as joining only if the renderer was previously disabled.
+ boolean joining = !wasRendererEnabled && playing;
+ // Enable the renderer.
+ enabledRendererCount++;
+ renderer.enable(
+ rendererConfiguration,
+ formats,
+ periodHolder.sampleStreams[rendererIndex],
+ rendererPositionUs,
+ joining,
+ mayRenderStartOfStream,
+ periodHolder.getRendererOffset());
+ mediaClock.onRendererEnabled(renderer);
+ // Start the renderer if playing.
+ if (playing) {
+ renderer.start();
}
}
@@ -2052,17 +2113,18 @@
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
}
- private void sendPlaybackParametersChangedInternal(
- PlaybackParameters playbackParameters, boolean acknowledgeCommand) {
+ private void sendPlaybackSpeedChangedInternal(float playbackSpeed, boolean acknowledgeCommand) {
handler
.obtainMessage(
- MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL,
- acknowledgeCommand ? 1 : 0,
- 0,
- playbackParameters)
+ MSG_PLAYBACK_SPEED_CHANGED_INTERNAL, acknowledgeCommand ? 1 : 0, 0, playbackSpeed)
.sendToTarget();
}
+ private boolean shouldPlayWhenReady() {
+ return playbackInfo.playWhenReady
+ && playbackInfo.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE;
+ }
+
private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
Timeline timeline,
PlaybackInfo playbackInfo,
@@ -2076,14 +2138,18 @@
return new PositionUpdateForPlaylistChange(
PlaybackInfo.getDummyPeriodForEmptyTimeline(),
/* periodPositionUs= */ 0,
- /* contentPositionUs= */ C.TIME_UNSET,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* forceBufferingState= */ false,
/* endPlayback= */ true);
}
MediaPeriodId oldPeriodId = playbackInfo.periodId;
Object newPeriodUid = oldPeriodId.periodUid;
+ boolean shouldUseRequestedContentPosition =
+ shouldUseRequestedContentPosition(playbackInfo, period, window);
long oldContentPositionUs =
- oldPeriodId.isAd() ? playbackInfo.contentPositionUs : playbackInfo.positionUs;
+ shouldUseRequestedContentPosition
+ ? playbackInfo.requestedContentPositionUs
+ : playbackInfo.positionUs;
long newContentPositionUs = oldContentPositionUs;
int startAtDefaultPositionWindowIndex = C.INDEX_UNSET;
boolean forceBufferingState = false;
@@ -2141,10 +2207,21 @@
startAtDefaultPositionWindowIndex =
timeline.getPeriodByUid(subsequentPeriodUid, period).windowIndex;
}
- } else if (oldContentPositionUs == C.TIME_UNSET) {
- // We previously set the content position to be the default position of the current window.
- // Re-resolve the period uid and position in case they changed since last time.
- startAtDefaultPositionWindowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
+ } else if (shouldUseRequestedContentPosition) {
+ // We previously requested a content position, but haven't used it yet. Re-resolve the
+ // requested window position to the period uid and position in case they changed.
+ if (oldContentPositionUs == C.TIME_UNSET) {
+ startAtDefaultPositionWindowIndex =
+ timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
+ } else {
+ playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period);
+ long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs();
+ int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex;
+ Pair<Object, Long> periodPosition =
+ timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
+ newPeriodUid = periodPosition.first;
+ newContentPositionUs = periodPosition.second;
+ }
}
// Set period uid for default positions and resolve position for ad resolution.
@@ -2158,15 +2235,12 @@
/* windowPositionUs= */ C.TIME_UNSET);
newPeriodUid = defaultPosition.first;
contentPositionForAdResolutionUs = defaultPosition.second;
+ newContentPositionUs = C.TIME_UNSET;
}
// Ensure ad insertion metadata is up to date.
MediaPeriodId periodIdWithAds =
queue.resolveMediaPeriodIdForAds(timeline, newPeriodUid, contentPositionForAdResolutionUs);
- if (!periodIdWithAds.isAd() && newContentPositionUs == C.TIME_UNSET) {
- // We are not going to play an ad, so use resolved content position.
- newContentPositionUs = contentPositionForAdResolutionUs;
- }
boolean oldAndNewPeriodIdAreSame =
oldPeriodId.periodUid.equals(newPeriodUid)
&& !oldPeriodId.isAd()
@@ -2193,6 +2267,111 @@
newPeriodId, periodPositionUs, newContentPositionUs, forceBufferingState, endPlayback);
}
+ private static boolean shouldUseRequestedContentPosition(
+ PlaybackInfo playbackInfo, Timeline.Period period, Timeline.Window window) {
+ // Only use the actual position as content position if it's not an ad and we already have
+ // prepared media information. Otherwise use the requested position.
+ MediaPeriodId periodId = playbackInfo.periodId;
+ Timeline timeline = playbackInfo.timeline;
+ return periodId.isAd()
+ || timeline.isEmpty()
+ || timeline.getWindow(
+ timeline.getPeriodByUid(periodId.periodUid, period).windowIndex, window)
+ .isPlaceholder;
+ }
+
+ /**
+ * Updates pending message to a new timeline.
+ *
+ * @param pendingMessageInfo The pending message.
+ * @param newTimeline The new timeline.
+ * @param previousTimeline The previous timeline used to set the message positions.
+ * @param repeatMode The current repeat mode.
+ * @param shuffleModeEnabled The current shuffle mode.
+ * @param window A scratch window.
+ * @param period A scratch period.
+ * @return Whether the message position could be resolved to the current timeline.
+ */
+ private static boolean resolvePendingMessagePosition(
+ PendingMessageInfo pendingMessageInfo,
+ Timeline newTimeline,
+ Timeline previousTimeline,
+ @Player.RepeatMode int repeatMode,
+ boolean shuffleModeEnabled,
+ Timeline.Window window,
+ Timeline.Period period) {
+ if (pendingMessageInfo.resolvedPeriodUid == null) {
+ // Position is still unresolved. Try to find window in new timeline.
+ long requestPositionUs =
+ pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE
+ ? C.TIME_UNSET
+ : C.msToUs(pendingMessageInfo.message.getPositionMs());
+ @Nullable
+ Pair<Object, Long> periodPosition =
+ resolveSeekPosition(
+ newTimeline,
+ new SeekPosition(
+ pendingMessageInfo.message.getTimeline(),
+ pendingMessageInfo.message.getWindowIndex(),
+ requestPositionUs),
+ /* trySubsequentPeriods= */ false,
+ repeatMode,
+ shuffleModeEnabled,
+ window,
+ period);
+ if (periodPosition == null) {
+ return false;
+ }
+ pendingMessageInfo.setResolvedPosition(
+ /* periodIndex= */ newTimeline.getIndexOfPeriod(periodPosition.first),
+ /* periodTimeUs= */ periodPosition.second,
+ /* periodUid= */ periodPosition.first);
+ if (pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE) {
+ resolvePendingMessageEndOfStreamPosition(newTimeline, pendingMessageInfo, window, period);
+ }
+ return true;
+ }
+ // Position has been resolved for a previous timeline. Try to find the updated period index.
+ int index = newTimeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid);
+ if (index == C.INDEX_UNSET) {
+ return false;
+ }
+ if (pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE) {
+ // Re-resolve end of stream in case the duration changed.
+ resolvePendingMessageEndOfStreamPosition(newTimeline, pendingMessageInfo, window, period);
+ return true;
+ }
+ pendingMessageInfo.resolvedPeriodIndex = index;
+ previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period);
+ if (previousTimeline.getWindow(period.windowIndex, window).isPlaceholder) {
+ // The position needs to be re-resolved because the window in the previous timeline wasn't
+ // fully prepared.
+ long windowPositionUs =
+ pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs();
+ int windowIndex =
+ newTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period).windowIndex;
+ Pair<Object, Long> periodPosition =
+ newTimeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
+ pendingMessageInfo.setResolvedPosition(
+ /* periodIndex= */ newTimeline.getIndexOfPeriod(periodPosition.first),
+ /* periodTimeUs= */ periodPosition.second,
+ /* periodUid= */ periodPosition.first);
+ }
+ return true;
+ }
+
+ private static void resolvePendingMessageEndOfStreamPosition(
+ Timeline timeline,
+ PendingMessageInfo messageInfo,
+ Timeline.Window window,
+ Timeline.Period period) {
+ int windowIndex = timeline.getPeriodByUid(messageInfo.resolvedPeriodUid, period).windowIndex;
+ int lastPeriodIndex = timeline.getWindow(windowIndex, window).lastPeriodIndex;
+ Object lastPeriodUid = timeline.getPeriod(lastPeriodIndex, period, /* setIds= */ true).uid;
+ long positionUs = period.durationUs != C.TIME_UNSET ? period.durationUs - 1 : Long.MAX_VALUE;
+ messageInfo.setResolvedPosition(lastPeriodIndex, positionUs, lastPeriodUid);
+ }
+
/**
* Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the
* internal timeline.
@@ -2241,6 +2420,15 @@
int periodIndex = timeline.getIndexOfPeriod(periodPosition.first);
if (periodIndex != C.INDEX_UNSET) {
// We successfully located the period in the internal timeline.
+ seekTimeline.getPeriodByUid(periodPosition.first, period);
+ if (seekTimeline.getWindow(period.windowIndex, window).isPlaceholder) {
+ // The seek timeline was using a placeholder, so we need to re-resolve using the updated
+ // timeline in case the resolved position changed.
+ int newWindowIndex = timeline.getPeriodByUid(periodPosition.first, period).windowIndex;
+ periodPosition =
+ timeline.getPeriodPosition(
+ window, period, newWindowIndex, seekPosition.windowPositionUs);
+ }
return periodPosition;
}
if (trySubsequentPeriods) {
@@ -2316,6 +2504,10 @@
return formats;
}
+ private static boolean isRendererEnabled(Renderer renderer) {
+ return renderer.getState() != Renderer.STATE_DISABLED;
+ }
+
private static final class SeekPosition {
public final Timeline timeline;
@@ -2332,19 +2524,19 @@
private static final class PositionUpdateForPlaylistChange {
public final MediaPeriodId periodId;
public final long periodPositionUs;
- public final long contentPositionUs;
+ public final long requestedContentPositionUs;
public final boolean forceBufferingState;
public final boolean endPlayback;
public PositionUpdateForPlaylistChange(
MediaPeriodId periodId,
long periodPositionUs,
- long contentPositionUs,
+ long requestedContentPositionUs,
boolean forceBufferingState,
boolean endPlayback) {
this.periodId = periodId;
this.periodPositionUs = periodPositionUs;
- this.contentPositionUs = contentPositionUs;
+ this.requestedContentPositionUs = requestedContentPositionUs;
this.forceBufferingState = forceBufferingState;
this.endPlayback = endPlayback;
}
@@ -2387,15 +2579,15 @@
}
}
- private static final class PlaylistUpdateMessage {
+ private static final class MediaSourceListUpdateMessage {
- private final List<Playlist.MediaSourceHolder> mediaSourceHolders;
+ private final List<MediaSourceList.MediaSourceHolder> mediaSourceHolders;
private final ShuffleOrder shuffleOrder;
private final int windowIndex;
private final long positionUs;
- private PlaylistUpdateMessage(
- List<Playlist.MediaSourceHolder> mediaSourceHolders,
+ private MediaSourceListUpdateMessage(
+ List<MediaSourceList.MediaSourceHolder> mediaSourceHolders,
ShuffleOrder shuffleOrder,
int windowIndex,
long positionUs) {
@@ -2422,27 +2614,31 @@
}
}
- private static final class PlaybackInfoUpdate {
+ /* package */ static final class PlaybackInfoUpdate {
- private PlaybackInfo lastPlaybackInfo;
- private int operationAcks;
- private boolean positionDiscontinuity;
- @DiscontinuityReason private int discontinuityReason;
+ private boolean hasPendingChange;
- public boolean hasPendingUpdate(PlaybackInfo playbackInfo) {
- return playbackInfo != lastPlaybackInfo || operationAcks > 0 || positionDiscontinuity;
- }
+ public PlaybackInfo playbackInfo;
+ public int operationAcks;
+ public boolean positionDiscontinuity;
+ @DiscontinuityReason public int discontinuityReason;
+ public boolean hasPlayWhenReadyChangeReason;
+ @PlayWhenReadyChangeReason public int playWhenReadyChangeReason;
- public void reset(PlaybackInfo playbackInfo) {
- lastPlaybackInfo = playbackInfo;
- operationAcks = 0;
- positionDiscontinuity = false;
+ public PlaybackInfoUpdate(PlaybackInfo playbackInfo) {
+ this.playbackInfo = playbackInfo;
}
public void incrementPendingOperationAcks(int operationAcks) {
+ hasPendingChange |= operationAcks > 0;
this.operationAcks += operationAcks;
}
+ public void setPlaybackInfo(PlaybackInfo playbackInfo) {
+ hasPendingChange |= this.playbackInfo != playbackInfo;
+ this.playbackInfo = playbackInfo;
+ }
+
public void setPositionDiscontinuity(@DiscontinuityReason int discontinuityReason) {
if (positionDiscontinuity
&& this.discontinuityReason != Player.DISCONTINUITY_REASON_INTERNAL) {
@@ -2451,8 +2647,16 @@
Assertions.checkArgument(discontinuityReason == Player.DISCONTINUITY_REASON_INTERNAL);
return;
}
+ hasPendingChange = true;
positionDiscontinuity = true;
this.discontinuityReason = discontinuityReason;
}
+
+ public void setPlayWhenReadyChangeReason(
+ @PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
+ hasPendingChange = true;
+ this.hasPlayWhenReadyChangeReason = true;
+ this.playWhenReadyChangeReason = playWhenReadyChangeReason;
+ }
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java
index 7d21182..13c0659 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java
@@ -23,20 +23,14 @@
*/
public final class FormatHolder {
- /** Whether the {@link #format} setter also sets the {@link #drmSession} field. */
- // TODO: Remove once all Renderers and MediaSources have migrated to the new DRM model [Internal
- // ref: b/129764794].
- public boolean includesDrmSession;
-
/** An accompanying context for decrypting samples in the format. */
- @Nullable public DrmSession<?> drmSession;
+ @Nullable public DrmSession drmSession;
/** The held {@link Format}. */
@Nullable public Format format;
/** Clears the holder. */
public void clear() {
- includesDrmSession = false;
drmSession = null;
format = null;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java
index 80be0b9..d91830a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java
@@ -87,14 +87,28 @@
*/
boolean retainBackBufferFromKeyframe();
+ /** @deprecated Use {@link LoadControl#shouldContinueLoading(long, long, float)}. */
+ @Deprecated
+ default boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
+ return false;
+ }
+
/**
* Called by the player to determine whether it should continue to load the source.
*
+ * @param playbackPositionUs The current playback position in microseconds, relative to the start
+ * of the {@link Timeline.Period period} that will continue to be loaded if this method
+ * returns {@code true}. If the playback for this period has not yet started, the value will
+ * negative and equal in magnitude to the duration of any media in previous periods still to
+ * be played.
* @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed.
* @return Whether the loading should continue.
*/
- boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed);
+ default boolean shouldContinueLoading(
+ long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
+ return shouldContinueLoading(bufferedDurationUs, playbackSpeed);
+ }
/**
* Called repeatedly by the player when it's loading the source, has yet to start playback, and
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
index 5bbbcbe..3c7a414 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
@@ -51,11 +51,18 @@
public boolean hasEnabledTracks;
/** {@link MediaPeriodInfo} about this media period. */
public MediaPeriodInfo info;
+ /**
+ * Whether all required renderers have been enabled with the {@link #sampleStreams} for this
+ * {@link #mediaPeriod}. This means either {@link Renderer#enable(RendererConfiguration, Format[],
+ * SampleStream, long, boolean, boolean, long)} or {@link Renderer#replaceStream(Format[],
+ * SampleStream, long)} has been called.
+ */
+ public boolean allRenderersEnabled;
private final boolean[] mayRetainStreamFlags;
private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector;
- private final Playlist playlist;
+ private final MediaSourceList mediaSourceList;
@Nullable private MediaPeriodHolder next;
private TrackGroupArray trackGroups;
@@ -69,7 +76,7 @@
* @param rendererPositionOffsetUs The renderer time of the start of the period, in microseconds.
* @param trackSelector The track selector.
* @param allocator The allocator.
- * @param playlist The playlist.
+ * @param mediaSourceList The playlist.
* @param info Information used to identify this media period in its timeline period.
* @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
* renderer.
@@ -79,13 +86,13 @@
long rendererPositionOffsetUs,
TrackSelector trackSelector,
Allocator allocator,
- Playlist playlist,
+ MediaSourceList mediaSourceList,
MediaPeriodInfo info,
TrackSelectorResult emptyTrackSelectorResult) {
this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs;
this.trackSelector = trackSelector;
- this.playlist = playlist;
+ this.mediaSourceList = mediaSourceList;
this.uid = info.id.periodUid;
this.info = info;
this.trackGroups = TrackGroupArray.EMPTY;
@@ -93,7 +100,8 @@
sampleStreams = new SampleStream[rendererCapabilities.length];
mayRetainStreamFlags = new boolean[rendererCapabilities.length];
mediaPeriod =
- createMediaPeriod(info.id, playlist, allocator, info.startPositionUs, info.endPositionUs);
+ createMediaPeriod(
+ info.id, mediaSourceList, allocator, info.startPositionUs, info.endPositionUs);
}
/**
@@ -171,9 +179,14 @@
prepared = true;
trackGroups = mediaPeriod.getTrackGroups();
TrackSelectorResult selectorResult = selectTracks(playbackSpeed, timeline);
+ long requestedStartPositionUs = info.startPositionUs;
+ if (info.durationUs != C.TIME_UNSET && requestedStartPositionUs >= info.durationUs) {
+ // Make sure start position doesn't exceed period duration.
+ requestedStartPositionUs = Math.max(0, info.durationUs - 1);
+ }
long newStartPositionUs =
applyTrackSelection(
- selectorResult, info.startPositionUs, /* forceRecreateStreams= */ false);
+ selectorResult, requestedStartPositionUs, /* forceRecreateStreams= */ false);
rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
info = info.copyWithStartPositionUs(newStartPositionUs);
}
@@ -303,7 +316,7 @@
/** Releases the media period. No other method should be called after the release. */
public void release() {
disableTrackSelectionsInResult();
- releaseMediaPeriod(info.endPositionUs, playlist, mediaPeriod);
+ releaseMediaPeriod(info.endPositionUs, mediaSourceList, mediaPeriod);
}
/**
@@ -400,11 +413,11 @@
/** Returns a media period corresponding to the given {@code id}. */
private static MediaPeriod createMediaPeriod(
MediaPeriodId id,
- Playlist playlist,
+ MediaSourceList mediaSourceList,
Allocator allocator,
long startPositionUs,
long endPositionUs) {
- MediaPeriod mediaPeriod = playlist.createPeriod(id, allocator, startPositionUs);
+ MediaPeriod mediaPeriod = mediaSourceList.createPeriod(id, allocator, startPositionUs);
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
mediaPeriod =
new ClippingMediaPeriod(
@@ -415,12 +428,12 @@
/** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
private static void releaseMediaPeriod(
- long endPositionUs, Playlist playlist, MediaPeriod mediaPeriod) {
+ long endPositionUs, MediaSourceList mediaSourceList, MediaPeriod mediaPeriod) {
try {
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
- playlist.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
+ mediaSourceList.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
} else {
- playlist.releasePeriod(mediaPeriod);
+ mediaSourceList.releasePeriod(mediaPeriod);
}
} catch (RuntimeException e) {
// There's nothing we can do.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java
index 2733df7..b14af5e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java
@@ -28,11 +28,13 @@
/** The start position of the media to play within the media period, in microseconds. */
public final long startPositionUs;
/**
- * If this is an ad, the position to play in the next content media period. {@link C#TIME_UNSET}
- * if this is not an ad or the next content media period should be played from its default
- * position.
+ * The requested next start position for the current timeline period, in microseconds, or {@link
+ * C#TIME_UNSET} if the period was requested to start at its default position.
+ *
+ * <p>Note that if {@link #id} refers to an ad, this is the requested start position for the
+ * suspended content.
*/
- public final long contentPositionUs;
+ public final long requestedContentPositionUs;
/**
* The end position to which the media period's content is clipped in order to play a following ad
* group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if this
@@ -51,6 +53,8 @@
* period corresponding to a timeline period without ads).
*/
public final boolean isLastInTimelinePeriod;
+ /** Whether this is the last media period in its timeline window. */
+ public final boolean isLastInTimelineWindow;
/**
* Whether this is the last media period in the entire timeline. If true, {@link
* #isLastInTimelinePeriod} will also be true.
@@ -60,17 +64,19 @@
MediaPeriodInfo(
MediaPeriodId id,
long startPositionUs,
- long contentPositionUs,
+ long requestedContentPositionUs,
long endPositionUs,
long durationUs,
boolean isLastInTimelinePeriod,
+ boolean isLastInTimelineWindow,
boolean isFinal) {
this.id = id;
this.startPositionUs = startPositionUs;
- this.contentPositionUs = contentPositionUs;
+ this.requestedContentPositionUs = requestedContentPositionUs;
this.endPositionUs = endPositionUs;
this.durationUs = durationUs;
this.isLastInTimelinePeriod = isLastInTimelinePeriod;
+ this.isLastInTimelineWindow = isLastInTimelineWindow;
this.isFinal = isFinal;
}
@@ -84,27 +90,29 @@
: new MediaPeriodInfo(
id,
startPositionUs,
- contentPositionUs,
+ requestedContentPositionUs,
endPositionUs,
durationUs,
isLastInTimelinePeriod,
+ isLastInTimelineWindow,
isFinal);
}
/**
- * Returns a copy of this instance with the content position set to the specified value. May
- * return the same instance if nothing changed.
+ * Returns a copy of this instance with the requested content position set to the specified value.
+ * May return the same instance if nothing changed.
*/
- public MediaPeriodInfo copyWithContentPositionUs(long contentPositionUs) {
- return contentPositionUs == this.contentPositionUs
+ public MediaPeriodInfo copyWithRequestedContentPositionUs(long requestedContentPositionUs) {
+ return requestedContentPositionUs == this.requestedContentPositionUs
? this
: new MediaPeriodInfo(
id,
startPositionUs,
- contentPositionUs,
+ requestedContentPositionUs,
endPositionUs,
durationUs,
isLastInTimelinePeriod,
+ isLastInTimelineWindow,
isFinal);
}
@@ -118,10 +126,11 @@
}
MediaPeriodInfo that = (MediaPeriodInfo) o;
return startPositionUs == that.startPositionUs
- && contentPositionUs == that.contentPositionUs
+ && requestedContentPositionUs == that.requestedContentPositionUs
&& endPositionUs == that.endPositionUs
&& durationUs == that.durationUs
&& isLastInTimelinePeriod == that.isLastInTimelinePeriod
+ && isLastInTimelineWindow == that.isLastInTimelineWindow
&& isFinal == that.isFinal
&& Util.areEqual(id, that.id);
}
@@ -131,10 +140,11 @@
int result = 17;
result = 31 * result + id.hashCode();
result = 31 * result + (int) startPositionUs;
- result = 31 * result + (int) contentPositionUs;
+ result = 31 * result + (int) requestedContentPositionUs;
result = 31 * result + (int) endPositionUs;
result = 31 * result + (int) durationUs;
result = 31 * result + (isLastInTimelinePeriod ? 1 : 0);
+ result = 31 * result + (isLastInTimelineWindow ? 1 : 0);
result = 31 * result + (isFinal ? 1 : 0);
return result;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
index f72e83a..a749f09 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
@@ -132,7 +132,7 @@
* @param rendererCapabilities The renderer capabilities.
* @param trackSelector The track selector.
* @param allocator The allocator.
- * @param playlist The playlist.
+ * @param mediaSourceList The list of media sources.
* @param info Information used to identify this media period in its timeline period.
* @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
* renderer.
@@ -141,13 +141,13 @@
RendererCapabilities[] rendererCapabilities,
TrackSelector trackSelector,
Allocator allocator,
- Playlist playlist,
+ MediaSourceList mediaSourceList,
MediaPeriodInfo info,
TrackSelectorResult emptyTrackSelectorResult) {
long rendererPositionOffsetUs =
loading == null
- ? (info.id.isAd() && info.contentPositionUs != C.TIME_UNSET
- ? info.contentPositionUs
+ ? (info.id.isAd() && info.requestedContentPositionUs != C.TIME_UNSET
+ ? info.requestedContentPositionUs
: 0)
: (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs);
MediaPeriodHolder newPeriodHolder =
@@ -156,7 +156,7 @@
rendererPositionOffsetUs,
trackSelector,
allocator,
- playlist,
+ mediaSourceList,
info,
emptyTrackSelectorResult);
if (loading != null) {
@@ -314,8 +314,11 @@
}
}
- // Use new period info, but keep old content position.
- periodHolder.info = newPeriodInfo.copyWithContentPositionUs(oldPeriodInfo.contentPositionUs);
+ // Use the new period info, but keep the old requested content position to avoid overriding it
+ // by the default content position generated in getFollowingMediaPeriodInfo.
+ periodHolder.info =
+ newPeriodInfo.copyWithRequestedContentPositionUs(
+ oldPeriodInfo.requestedContentPositionUs);
if (!areDurationsCompatible(oldPeriodInfo.durationUs, newPeriodInfo.durationUs)) {
// The period duration changed. Remove all subsequent periods and check whether we read
@@ -350,6 +353,7 @@
public MediaPeriodInfo getUpdatedMediaPeriodInfo(Timeline timeline, MediaPeriodInfo info) {
MediaPeriodId id = info.id;
boolean isLastInPeriod = isLastInPeriod(id);
+ boolean isLastInWindow = isLastInWindow(timeline, id);
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
timeline.getPeriodByUid(info.id.periodUid, period);
long durationUs =
@@ -361,10 +365,11 @@
return new MediaPeriodInfo(
id,
info.startPositionUs,
- info.contentPositionUs,
+ info.requestedContentPositionUs,
info.endPositionUs,
durationUs,
isLastInPeriod,
+ isLastInWindow,
isLastInTimeline);
}
@@ -534,7 +539,7 @@
return getMediaPeriodInfo(
playbackInfo.timeline,
playbackInfo.periodId,
- playbackInfo.contentPositionUs,
+ playbackInfo.requestedContentPositionUs,
playbackInfo.positionUs);
}
@@ -631,11 +636,11 @@
currentPeriodId.periodUid,
adGroupIndex,
nextAdIndexInAdGroup,
- mediaPeriodInfo.contentPositionUs,
+ mediaPeriodInfo.requestedContentPositionUs,
currentPeriodId.windowSequenceNumber);
} else {
// Play content from the ad group position.
- long startPositionUs = mediaPeriodInfo.contentPositionUs;
+ long startPositionUs = mediaPeriodInfo.requestedContentPositionUs;
if (startPositionUs == C.TIME_UNSET) {
// If we're transitioning from an ad group to content starting from its default position,
// project the start position forward as if this were a transition to a new window.
@@ -656,6 +661,7 @@
timeline,
currentPeriodId.periodUid,
startPositionUs,
+ mediaPeriodInfo.requestedContentPositionUs,
currentPeriodId.windowSequenceNumber);
}
} else {
@@ -667,6 +673,7 @@
timeline,
currentPeriodId.periodUid,
/* startPositionUs= */ mediaPeriodInfo.durationUs,
+ /* requestedContentPositionUs= */ mediaPeriodInfo.durationUs,
currentPeriodId.windowSequenceNumber);
}
int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex);
@@ -683,7 +690,7 @@
}
private MediaPeriodInfo getMediaPeriodInfo(
- Timeline timeline, MediaPeriodId id, long contentPositionUs, long startPositionUs) {
+ Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
timeline.getPeriodByUid(id.periodUid, period);
if (id.isAd()) {
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
@@ -694,11 +701,15 @@
id.periodUid,
id.adGroupIndex,
id.adIndexInAdGroup,
- contentPositionUs,
+ requestedContentPositionUs,
id.windowSequenceNumber);
} else {
return getMediaPeriodInfoForContent(
- timeline, id.periodUid, startPositionUs, id.windowSequenceNumber);
+ timeline,
+ id.periodUid,
+ startPositionUs,
+ requestedContentPositionUs,
+ id.windowSequenceNumber);
}
}
@@ -719,6 +730,10 @@
adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex)
? period.getAdResumePositionUs()
: 0;
+ if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) {
+ // Ensure start position doesn't exceed duration.
+ startPositionUs = Math.max(0, durationUs - 1);
+ }
return new MediaPeriodInfo(
id,
startPositionUs,
@@ -726,14 +741,21 @@
/* endPositionUs= */ C.TIME_UNSET,
durationUs,
/* isLastInTimelinePeriod= */ false,
+ /* isLastInTimelineWindow= */ false,
/* isFinal= */ false);
}
private MediaPeriodInfo getMediaPeriodInfoForContent(
- Timeline timeline, Object periodUid, long startPositionUs, long windowSequenceNumber) {
+ Timeline timeline,
+ Object periodUid,
+ long startPositionUs,
+ long requestedContentPositionUs,
+ long windowSequenceNumber) {
+ timeline.getPeriodByUid(periodUid, period);
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex);
boolean isLastInPeriod = isLastInPeriod(id);
+ boolean isLastInWindow = isLastInWindow(timeline, id);
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
long endPositionUs =
nextAdGroupIndex != C.INDEX_UNSET
@@ -743,13 +765,18 @@
endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE
? period.durationUs
: endPositionUs;
+ if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) {
+ // Ensure start position doesn't exceed duration.
+ startPositionUs = Math.max(0, durationUs - 1);
+ }
return new MediaPeriodInfo(
id,
startPositionUs,
- /* contentPositionUs= */ C.TIME_UNSET,
+ requestedContentPositionUs,
endPositionUs,
durationUs,
isLastInPeriod,
+ isLastInWindow,
isLastInTimeline);
}
@@ -757,6 +784,15 @@
return !id.isAd() && id.nextAdGroupIndex == C.INDEX_UNSET;
}
+ private boolean isLastInWindow(Timeline timeline, MediaPeriodId id) {
+ if (!isLastInPeriod(id)) {
+ return false;
+ }
+ int windowIndex = timeline.getPeriodByUid(id.periodUid, period).windowIndex;
+ int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
+ return timeline.getWindow(windowIndex, window).lastPeriodIndex == periodIndex;
+ }
+
private boolean isLastInTimeline(
Timeline timeline, MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java
new file mode 100644
index 0000000..1ffb8f5
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/MediaSourceList.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import android.os.Handler;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.analytics.AnalyticsCollector;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
+import com.google.android.exoplayer2.source.LoadEventInfo;
+import com.google.android.exoplayer2.source.MaskingMediaPeriod;
+import com.google.android.exoplayer2.source.MaskingMediaSource;
+import com.google.android.exoplayer2.source.MediaLoadData;
+import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceEventListener;
+import com.google.android.exoplayer2.source.ShuffleOrder;
+import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
+import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
+ * during playback. It is valid for the same {@link MediaSource} instance to be present more than
+ * once in the playlist.
+ *
+ * <p>With the exception of the constructor, all methods are called on the playback thread.
+ */
+/* package */ class MediaSourceList {
+
+ /** Listener for source events. */
+ public interface MediaSourceListInfoRefreshListener {
+
+ /**
+ * Called when the timeline of a media item has changed and a new timeline that reflects the
+ * current playlist state needs to be created by calling {@link #createTimeline()}.
+ *
+ * <p>Called on the playback thread.
+ */
+ void onPlaylistUpdateRequested();
+ }
+
+ private final List<MediaSourceHolder> mediaSourceHolders;
+ private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
+ private final Map<Object, MediaSourceHolder> mediaSourceByUid;
+ private final MediaSourceListInfoRefreshListener mediaSourceListInfoListener;
+ private final MediaSourceEventListener.EventDispatcher eventDispatcher;
+ private final HashMap<MediaSourceList.MediaSourceHolder, MediaSourceAndListener> childSources;
+ private final Set<MediaSourceHolder> enabledMediaSourceHolders;
+
+ private ShuffleOrder shuffleOrder;
+ private boolean isPrepared;
+
+ @Nullable private TransferListener mediaTransferListener;
+
+ @SuppressWarnings("initialization")
+ public MediaSourceList(MediaSourceListInfoRefreshListener listener) {
+ mediaSourceListInfoListener = listener;
+ shuffleOrder = new DefaultShuffleOrder(0);
+ mediaSourceByMediaPeriod = new IdentityHashMap<>();
+ mediaSourceByUid = new HashMap<>();
+ mediaSourceHolders = new ArrayList<>();
+ eventDispatcher = new MediaSourceEventListener.EventDispatcher();
+ childSources = new HashMap<>();
+ enabledMediaSourceHolders = new HashSet<>();
+ }
+
+ /**
+ * Sets the media sources replacing any sources previously contained in the playlist.
+ *
+ * @param holders The list of {@link MediaSourceHolder}s to set.
+ * @param shuffleOrder The new shuffle order.
+ * @return The new {@link Timeline}.
+ */
+ public final Timeline setMediaSources(
+ List<MediaSourceHolder> holders, ShuffleOrder shuffleOrder) {
+ removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolders.size());
+ return addMediaSources(/* index= */ this.mediaSourceHolders.size(), holders, shuffleOrder);
+ }
+
+ /**
+ * Adds multiple {@link MediaSourceHolder}s to the playlist.
+ *
+ * @param index The index at which the new {@link MediaSourceHolder}s will be inserted. This index
+ * must be in the range of 0 <= index <= {@link #getSize()}.
+ * @param holders A list of {@link MediaSourceHolder}s to be added.
+ * @param shuffleOrder The new shuffle order.
+ * @return The new {@link Timeline}.
+ */
+ public final Timeline addMediaSources(
+ int index, List<MediaSourceHolder> holders, ShuffleOrder shuffleOrder) {
+ if (!holders.isEmpty()) {
+ this.shuffleOrder = shuffleOrder;
+ for (int insertionIndex = index; insertionIndex < index + holders.size(); insertionIndex++) {
+ MediaSourceHolder holder = holders.get(insertionIndex - index);
+ if (insertionIndex > 0) {
+ MediaSourceHolder previousHolder = mediaSourceHolders.get(insertionIndex - 1);
+ Timeline previousTimeline = previousHolder.mediaSource.getTimeline();
+ holder.reset(
+ /* firstWindowIndexInChild= */ previousHolder.firstWindowIndexInChild
+ + previousTimeline.getWindowCount());
+ } else {
+ holder.reset(/* firstWindowIndexInChild= */ 0);
+ }
+ Timeline newTimeline = holder.mediaSource.getTimeline();
+ correctOffsets(
+ /* startIndex= */ insertionIndex,
+ /* windowOffsetUpdate= */ newTimeline.getWindowCount());
+ mediaSourceHolders.add(insertionIndex, holder);
+ mediaSourceByUid.put(holder.uid, holder);
+ if (isPrepared) {
+ prepareChildSource(holder);
+ if (mediaSourceByMediaPeriod.isEmpty()) {
+ enabledMediaSourceHolders.add(holder);
+ } else {
+ disableChildSource(holder);
+ }
+ }
+ }
+ }
+ return createTimeline();
+ }
+
+ /**
+ * Removes a range of {@link MediaSourceHolder}s from the playlist, by specifying an initial index
+ * (included) and a final index (excluded).
+ *
+ * <p>Note: when specified range is empty, no actual media source is removed and no exception is
+ * thrown.
+ *
+ * @param fromIndex The initial range index, pointing to the first media source that will be
+ * removed. This index must be in the range of 0 <= index <= {@link #getSize()}.
+ * @param toIndex The final range index, pointing to the first media source that will be left
+ * untouched. This index must be in the range of 0 <= index <= {@link #getSize()}.
+ * @param shuffleOrder The new shuffle order.
+ * @return The new {@link Timeline}.
+ * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
+ * {@code toIndex} > {@link #getSize()}, {@code fromIndex} > {@code toIndex}
+ */
+ public final Timeline removeMediaSourceRange(
+ int fromIndex, int toIndex, ShuffleOrder shuffleOrder) {
+ Assertions.checkArgument(fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize());
+ this.shuffleOrder = shuffleOrder;
+ removeMediaSourcesInternal(fromIndex, toIndex);
+ return createTimeline();
+ }
+
+ /**
+ * Moves an existing media source within the playlist.
+ *
+ * @param currentIndex The current index of the media source in the playlist. This index must be
+ * in the range of 0 <= index < {@link #getSize()}.
+ * @param newIndex The target index of the media source in the playlist. This index must be in the
+ * range of 0 <= index < {@link #getSize()}.
+ * @param shuffleOrder The new shuffle order.
+ * @return The new {@link Timeline}.
+ * @throws IllegalArgumentException When an index is invalid, i.e. {@code currentIndex} < 0,
+ * {@code currentIndex} >= {@link #getSize()}, {@code newIndex} < 0
+ */
+ public final Timeline moveMediaSource(int currentIndex, int newIndex, ShuffleOrder shuffleOrder) {
+ return moveMediaSourceRange(currentIndex, currentIndex + 1, newIndex, shuffleOrder);
+ }
+
+ /**
+ * Moves a range of media sources within the playlist.
+ *
+ * <p>Note: when specified range is empty or the from index equals the new from index, no actual
+ * media source is moved and no exception is thrown.
+ *
+ * @param fromIndex The initial range index, pointing to the first media source of the range that
+ * will be moved. This index must be in the range of 0 <= index <= {@link #getSize()}.
+ * @param toIndex The final range index, pointing to the first media source that will be left
+ * untouched. This index must be larger or equals than {@code fromIndex}.
+ * @param newFromIndex The target index of the first media source of the range that will be moved.
+ * @param shuffleOrder The new shuffle order.
+ * @return The new {@link Timeline}.
+ * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
+ * {@code toIndex} < {@code fromIndex}, {@code fromIndex} > {@code toIndex}, {@code
+ * newFromIndex} < 0
+ */
+ public Timeline moveMediaSourceRange(
+ int fromIndex, int toIndex, int newFromIndex, ShuffleOrder shuffleOrder) {
+ Assertions.checkArgument(
+ fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize() && newFromIndex >= 0);
+ this.shuffleOrder = shuffleOrder;
+ if (fromIndex == toIndex || fromIndex == newFromIndex) {
+ return createTimeline();
+ }
+ int startIndex = Math.min(fromIndex, newFromIndex);
+ int newEndIndex = newFromIndex + (toIndex - fromIndex) - 1;
+ int endIndex = Math.max(newEndIndex, toIndex - 1);
+ int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild;
+ moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
+ for (int i = startIndex; i <= endIndex; i++) {
+ MediaSourceHolder holder = mediaSourceHolders.get(i);
+ holder.firstWindowIndexInChild = windowOffset;
+ windowOffset += holder.mediaSource.getTimeline().getWindowCount();
+ }
+ return createTimeline();
+ }
+
+ /** Clears the playlist. */
+ public final Timeline clear(@Nullable ShuffleOrder shuffleOrder) {
+ this.shuffleOrder = shuffleOrder != null ? shuffleOrder : this.shuffleOrder.cloneAndClear();
+ removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ getSize());
+ return createTimeline();
+ }
+
+ /** Whether the playlist is prepared. */
+ public final boolean isPrepared() {
+ return isPrepared;
+ }
+
+ /** Returns the number of media sources in the playlist. */
+ public final int getSize() {
+ return mediaSourceHolders.size();
+ }
+
+ /**
+ * Sets the {@link AnalyticsCollector}.
+ *
+ * @param handler The handler on which to call the collector.
+ * @param analyticsCollector The analytics collector.
+ */
+ public final void setAnalyticsCollector(Handler handler, AnalyticsCollector analyticsCollector) {
+ eventDispatcher.addEventListener(handler, analyticsCollector, MediaSourceEventListener.class);
+ eventDispatcher.addEventListener(handler, analyticsCollector, DrmSessionEventListener.class);
+ }
+
+ /**
+ * Sets a new shuffle order to use when shuffling the child media sources.
+ *
+ * @param shuffleOrder A {@link ShuffleOrder}.
+ */
+ public final Timeline setShuffleOrder(ShuffleOrder shuffleOrder) {
+ int size = getSize();
+ if (shuffleOrder.getLength() != size) {
+ shuffleOrder =
+ shuffleOrder
+ .cloneAndClear()
+ .cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
+ }
+ this.shuffleOrder = shuffleOrder;
+ return createTimeline();
+ }
+
+ /** Prepares the playlist. */
+ public final void prepare(@Nullable TransferListener mediaTransferListener) {
+ Assertions.checkState(!isPrepared);
+ this.mediaTransferListener = mediaTransferListener;
+ for (int i = 0; i < mediaSourceHolders.size(); i++) {
+ MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
+ prepareChildSource(mediaSourceHolder);
+ enabledMediaSourceHolders.add(mediaSourceHolder);
+ }
+ isPrepared = true;
+ }
+
+ /**
+ * Returns a new {@link MediaPeriod} identified by {@code periodId}.
+ *
+ * @param id The identifier of the period.
+ * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
+ * @param startPositionUs The expected start position, in microseconds.
+ * @return A new {@link MediaPeriod}.
+ */
+ public MediaPeriod createPeriod(
+ MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs) {
+ Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
+ MediaSource.MediaPeriodId childMediaPeriodId =
+ id.copyWithPeriodUid(getChildPeriodUid(id.periodUid));
+ MediaSourceHolder holder = Assertions.checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid));
+ enableMediaSource(holder);
+ holder.activeMediaPeriodIds.add(childMediaPeriodId);
+ MediaPeriod mediaPeriod =
+ holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
+ mediaSourceByMediaPeriod.put(mediaPeriod, holder);
+ disableUnusedMediaSources();
+ return mediaPeriod;
+ }
+
+ /**
+ * Releases the period.
+ *
+ * @param mediaPeriod The period to release.
+ */
+ public final void releasePeriod(MediaPeriod mediaPeriod) {
+ MediaSourceHolder holder =
+ Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
+ holder.mediaSource.releasePeriod(mediaPeriod);
+ holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
+ if (!mediaSourceByMediaPeriod.isEmpty()) {
+ disableUnusedMediaSources();
+ }
+ maybeReleaseChildSource(holder);
+ }
+
+ /** Releases the playlist. */
+ public final void release() {
+ for (MediaSourceAndListener childSource : childSources.values()) {
+ childSource.mediaSource.releaseSource(childSource.caller);
+ childSource.mediaSource.removeEventListener(childSource.eventListener);
+ }
+ childSources.clear();
+ enabledMediaSourceHolders.clear();
+ isPrepared = false;
+ }
+
+ /** Throws any pending error encountered while loading or refreshing. */
+ public final void maybeThrowSourceInfoRefreshError() throws IOException {
+ for (MediaSourceAndListener childSource : childSources.values()) {
+ childSource.mediaSource.maybeThrowSourceInfoRefreshError();
+ }
+ }
+
+ /** Creates a timeline reflecting the current state of the playlist. */
+ public final Timeline createTimeline() {
+ if (mediaSourceHolders.isEmpty()) {
+ return Timeline.EMPTY;
+ }
+ int windowOffset = 0;
+ for (int i = 0; i < mediaSourceHolders.size(); i++) {
+ MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
+ mediaSourceHolder.firstWindowIndexInChild = windowOffset;
+ windowOffset += mediaSourceHolder.mediaSource.getTimeline().getWindowCount();
+ }
+ return new PlaylistTimeline(mediaSourceHolders, shuffleOrder);
+ }
+
+ // Internal methods.
+
+ private void enableMediaSource(MediaSourceHolder mediaSourceHolder) {
+ enabledMediaSourceHolders.add(mediaSourceHolder);
+ @Nullable MediaSourceAndListener enabledChild = childSources.get(mediaSourceHolder);
+ if (enabledChild != null) {
+ enabledChild.mediaSource.enable(enabledChild.caller);
+ }
+ }
+
+ private void disableUnusedMediaSources() {
+ Iterator<MediaSourceHolder> iterator = enabledMediaSourceHolders.iterator();
+ while (iterator.hasNext()) {
+ MediaSourceHolder holder = iterator.next();
+ if (holder.activeMediaPeriodIds.isEmpty()) {
+ disableChildSource(holder);
+ iterator.remove();
+ }
+ }
+ }
+
+ private void disableChildSource(MediaSourceHolder holder) {
+ @Nullable MediaSourceAndListener disabledChild = childSources.get(holder);
+ if (disabledChild != null) {
+ disabledChild.mediaSource.disable(disabledChild.caller);
+ }
+ }
+
+ private void removeMediaSourcesInternal(int fromIndex, int toIndex) {
+ for (int index = toIndex - 1; index >= fromIndex; index--) {
+ MediaSourceHolder holder = mediaSourceHolders.remove(index);
+ mediaSourceByUid.remove(holder.uid);
+ Timeline oldTimeline = holder.mediaSource.getTimeline();
+ correctOffsets(
+ /* startIndex= */ index, /* windowOffsetUpdate= */ -oldTimeline.getWindowCount());
+ holder.isRemoved = true;
+ if (isPrepared) {
+ maybeReleaseChildSource(holder);
+ }
+ }
+ }
+
+ private void correctOffsets(int startIndex, int windowOffsetUpdate) {
+ for (int i = startIndex; i < mediaSourceHolders.size(); i++) {
+ MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
+ mediaSourceHolder.firstWindowIndexInChild += windowOffsetUpdate;
+ }
+ }
+
+ // Internal methods to manage child sources.
+
+ @Nullable
+ private static MediaSource.MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
+ MediaSourceHolder mediaSourceHolder, MediaSource.MediaPeriodId mediaPeriodId) {
+ for (int i = 0; i < mediaSourceHolder.activeMediaPeriodIds.size(); i++) {
+ // Ensure the reported media period id has the same window sequence number as the one created
+ // by this media source. Otherwise it does not belong to this child source.
+ if (mediaSourceHolder.activeMediaPeriodIds.get(i).windowSequenceNumber
+ == mediaPeriodId.windowSequenceNumber) {
+ Object periodUid = getPeriodUid(mediaSourceHolder, mediaPeriodId.periodUid);
+ return mediaPeriodId.copyWithPeriodUid(periodUid);
+ }
+ }
+ return null;
+ }
+
+ private static int getWindowIndexForChildWindowIndex(
+ MediaSourceHolder mediaSourceHolder, int windowIndex) {
+ return windowIndex + mediaSourceHolder.firstWindowIndexInChild;
+ }
+
+ private void prepareChildSource(MediaSourceHolder holder) {
+ MediaSource mediaSource = holder.mediaSource;
+ MediaSource.MediaSourceCaller caller =
+ (source, timeline) -> mediaSourceListInfoListener.onPlaylistUpdateRequested();
+ ForwardingEventListener eventListener = new ForwardingEventListener(holder);
+ childSources.put(holder, new MediaSourceAndListener(mediaSource, caller, eventListener));
+ mediaSource.addEventListener(Util.createHandler(), eventListener);
+ mediaSource.addDrmEventListener(Util.createHandler(), eventListener);
+ mediaSource.prepareSource(caller, mediaTransferListener);
+ }
+
+ private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
+ // Release if the source has been removed from the playlist and no periods are still active.
+ if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
+ MediaSourceAndListener removedChild =
+ Assertions.checkNotNull(childSources.remove(mediaSourceHolder));
+ removedChild.mediaSource.releaseSource(removedChild.caller);
+ removedChild.mediaSource.removeEventListener(removedChild.eventListener);
+ enabledMediaSourceHolders.remove(mediaSourceHolder);
+ }
+ }
+
+ /** Return uid of media source holder from period uid of concatenated source. */
+ private static Object getMediaSourceHolderUid(Object periodUid) {
+ return PlaylistTimeline.getChildTimelineUidFromConcatenatedUid(periodUid);
+ }
+
+ /** Return uid of child period from period uid of concatenated source. */
+ private static Object getChildPeriodUid(Object periodUid) {
+ return PlaylistTimeline.getChildPeriodUidFromConcatenatedUid(periodUid);
+ }
+
+ private static Object getPeriodUid(MediaSourceHolder holder, Object childPeriodUid) {
+ return PlaylistTimeline.getConcatenatedUid(holder.uid, childPeriodUid);
+ }
+
+ /* package */ static void moveMediaSourceHolders(
+ List<MediaSourceHolder> mediaSourceHolders, int fromIndex, int toIndex, int newFromIndex) {
+ MediaSourceHolder[] removedItems = new MediaSourceHolder[toIndex - fromIndex];
+ for (int i = removedItems.length - 1; i >= 0; i--) {
+ removedItems[i] = mediaSourceHolders.remove(fromIndex + i);
+ }
+ mediaSourceHolders.addAll(
+ Math.min(newFromIndex, mediaSourceHolders.size()), Arrays.asList(removedItems));
+ }
+
+ /** Data class to hold playlist media sources together with meta data needed to process them. */
+ /* package */ static final class MediaSourceHolder {
+
+ public final MaskingMediaSource mediaSource;
+ public final Object uid;
+ public final List<MediaSource.MediaPeriodId> activeMediaPeriodIds;
+
+ public int firstWindowIndexInChild;
+ public boolean isRemoved;
+
+ public MediaSourceHolder(MediaSource mediaSource, boolean useLazyPreparation) {
+ this.mediaSource = new MaskingMediaSource(mediaSource, useLazyPreparation);
+ this.activeMediaPeriodIds = new ArrayList<>();
+ this.uid = new Object();
+ }
+
+ public void reset(int firstWindowIndexInChild) {
+ this.firstWindowIndexInChild = firstWindowIndexInChild;
+ this.isRemoved = false;
+ this.activeMediaPeriodIds.clear();
+ }
+ }
+
+ /** Timeline exposing concatenated timelines of playlist media sources. */
+ /* package */ static final class PlaylistTimeline extends AbstractConcatenatedTimeline {
+
+ private final int windowCount;
+ private final int periodCount;
+ private final int[] firstPeriodInChildIndices;
+ private final int[] firstWindowInChildIndices;
+ private final Timeline[] timelines;
+ private final Object[] uids;
+ private final HashMap<Object, Integer> childIndexByUid;
+
+ public PlaylistTimeline(
+ Collection<MediaSourceHolder> mediaSourceHolders, ShuffleOrder shuffleOrder) {
+ super(/* isAtomic= */ false, shuffleOrder);
+ int childCount = mediaSourceHolders.size();
+ firstPeriodInChildIndices = new int[childCount];
+ firstWindowInChildIndices = new int[childCount];
+ timelines = new Timeline[childCount];
+ uids = new Object[childCount];
+ childIndexByUid = new HashMap<>();
+ int index = 0;
+ int windowCount = 0;
+ int periodCount = 0;
+ for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
+ timelines[index] = mediaSourceHolder.mediaSource.getTimeline();
+ firstWindowInChildIndices[index] = windowCount;
+ firstPeriodInChildIndices[index] = periodCount;
+ windowCount += timelines[index].getWindowCount();
+ periodCount += timelines[index].getPeriodCount();
+ uids[index] = mediaSourceHolder.uid;
+ childIndexByUid.put(uids[index], index++);
+ }
+ this.windowCount = windowCount;
+ this.periodCount = periodCount;
+ }
+
+ @Override
+ protected int getChildIndexByPeriodIndex(int periodIndex) {
+ return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
+ }
+
+ @Override
+ protected int getChildIndexByWindowIndex(int windowIndex) {
+ return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
+ }
+
+ @Override
+ protected int getChildIndexByChildUid(Object childUid) {
+ Integer index = childIndexByUid.get(childUid);
+ return index == null ? C.INDEX_UNSET : index;
+ }
+
+ @Override
+ protected Timeline getTimelineByChildIndex(int childIndex) {
+ return timelines[childIndex];
+ }
+
+ @Override
+ protected int getFirstPeriodIndexByChildIndex(int childIndex) {
+ return firstPeriodInChildIndices[childIndex];
+ }
+
+ @Override
+ protected int getFirstWindowIndexByChildIndex(int childIndex) {
+ return firstWindowInChildIndices[childIndex];
+ }
+
+ @Override
+ protected Object getChildUidByChildIndex(int childIndex) {
+ return uids[childIndex];
+ }
+
+ @Override
+ public int getWindowCount() {
+ return windowCount;
+ }
+
+ @Override
+ public int getPeriodCount() {
+ return periodCount;
+ }
+ }
+
+ private static final class MediaSourceAndListener {
+
+ public final MediaSource mediaSource;
+ public final MediaSource.MediaSourceCaller caller;
+ public final MediaSourceEventListener eventListener;
+
+ public MediaSourceAndListener(
+ MediaSource mediaSource,
+ MediaSource.MediaSourceCaller caller,
+ MediaSourceEventListener eventListener) {
+ this.mediaSource = mediaSource;
+ this.caller = caller;
+ this.eventListener = eventListener;
+ }
+ }
+
+ private final class ForwardingEventListener
+ implements MediaSourceEventListener, DrmSessionEventListener {
+
+ private final MediaSourceList.MediaSourceHolder id;
+ private EventDispatcher eventDispatcher;
+
+ public ForwardingEventListener(MediaSourceList.MediaSourceHolder id) {
+ eventDispatcher = MediaSourceList.this.eventDispatcher;
+ this.id = id;
+ }
+
+ // MediaSourceEventListener implementation
+
+ @Override
+ public void onMediaPeriodCreated(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.mediaPeriodCreated();
+ }
+ }
+
+ @Override
+ public void onMediaPeriodReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.mediaPeriodReleased();
+ }
+ }
+
+ @Override
+ public void onLoadStarted(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventData,
+ MediaLoadData mediaLoadData) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.loadStarted(loadEventData, mediaLoadData);
+ }
+ }
+
+ @Override
+ public void onLoadCompleted(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventData,
+ MediaLoadData mediaLoadData) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.loadCompleted(loadEventData, mediaLoadData);
+ }
+ }
+
+ @Override
+ public void onLoadCanceled(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventData,
+ MediaLoadData mediaLoadData) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.loadCanceled(loadEventData, mediaLoadData);
+ }
+ }
+
+ @Override
+ public void onLoadError(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ LoadEventInfo loadEventData,
+ MediaLoadData mediaLoadData,
+ IOException error,
+ boolean wasCanceled) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.loadError(loadEventData, mediaLoadData, error, wasCanceled);
+ }
+ }
+
+ @Override
+ public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.readingStarted();
+ }
+ }
+
+ @Override
+ public void onUpstreamDiscarded(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ MediaLoadData mediaLoadData) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.upstreamDiscarded(mediaLoadData);
+ }
+ }
+
+ @Override
+ public void onDownstreamFormatChanged(
+ int windowIndex,
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId,
+ MediaLoadData mediaLoadData) {
+ if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
+ eventDispatcher.downstreamFormatChanged(mediaLoadData);
+ }
+ }
+
+ // DrmSessionEventListener implementation
+
+ @Override
+ public void onDrmSessionAcquired() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionAcquired(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysLoaded() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmSessionManagerError(Exception error) {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionManagerError(error),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysRestored() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRestored(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysRemoved() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRemoved(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmSessionReleased() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionReleased(),
+ DrmSessionEventListener.class);
+ }
+
+ /** Updates the event dispatcher and returns whether the event should be dispatched. */
+ private boolean maybeUpdateEventDispatcher(
+ int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) {
+ @Nullable MediaSource.MediaPeriodId mediaPeriodId = null;
+ if (childMediaPeriodId != null) {
+ mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId);
+ if (mediaPeriodId == null) {
+ // Media period not found. Ignore event.
+ return false;
+ }
+ }
+ int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex);
+ if (eventDispatcher.windowIndex != windowIndex
+ || !Util.areEqual(eventDispatcher.mediaPeriodId, mediaPeriodId)) {
+ eventDispatcher =
+ MediaSourceList.this.eventDispatcher.withParameters(
+ windowIndex, mediaPeriodId, /* mediaTimeOffsetMs= */ 0L);
+ }
+ return true;
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java
index 2889787..47ed8ce 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java
@@ -60,24 +60,15 @@
return state;
}
- /**
- * Replaces the {@link SampleStream} that will be associated with this renderer.
- * <p>
- * This method may be called when the renderer is in the following states:
- * {@link #STATE_DISABLED}.
- *
- * @param configuration The renderer configuration.
- * @param formats The enabled formats. Should be empty.
- * @param stream The {@link SampleStream} from which the renderer should consume.
- * @param positionUs The player's current position.
- * @param joining Whether this renderer is being enabled to join an ongoing playback.
- * @param offsetUs The offset that should be subtracted from {@code positionUs}
- * to get the playback position with respect to the media.
- * @throws ExoPlaybackException If an error occurs.
- */
@Override
- public final void enable(RendererConfiguration configuration, Format[] formats,
- SampleStream stream, long positionUs, boolean joining, long offsetUs)
+ public final void enable(
+ RendererConfiguration configuration,
+ Format[] formats,
+ SampleStream stream,
+ long positionUs,
+ boolean joining,
+ boolean mayRenderStartOfStream,
+ long offsetUs)
throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration;
@@ -94,18 +85,6 @@
onStarted();
}
- /**
- * Replaces the {@link SampleStream} that will be associated with this renderer.
- * <p>
- * This method may be called when the renderer is in the following states:
- * {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
- *
- * @param formats The enabled formats. Should be empty.
- * @param stream The {@link SampleStream} to be associated with this renderer.
- * @param offsetUs The offset that should be subtracted from {@code positionUs} in
- * {@link #render(long, long)} to get the playback position with respect to the media.
- * @throws ExoPlaybackException If an error occurs.
- */
@Override
public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs)
throws ExoPlaybackException {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
index 72f4dab..f183af0 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
@@ -17,6 +17,7 @@
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
@@ -38,12 +39,14 @@
/** The {@link MediaPeriodId} of the currently playing media period in the {@link #timeline}. */
public final MediaPeriodId periodId;
/**
- * If {@link #periodId} refers to an ad, the position of the suspended content relative to the
- * start of the associated period in the {@link #timeline}, in microseconds. {@link C#TIME_UNSET}
- * if {@link #periodId} does not refer to an ad or if the suspended content should be played from
- * its default position.
+ * The requested next start position for the current period in the {@link #timeline}, in
+ * microseconds, or {@link C#TIME_UNSET} if the period was requested to start at its default
+ * position.
+ *
+ * <p>Note that if {@link #periodId} refers to an ad, this is the requested start position for the
+ * suspended content.
*/
- public final long contentPositionUs;
+ public final long requestedContentPositionUs;
/** The current playback state. One of the {@link Player}.STATE_ constants. */
@Player.State public final int playbackState;
/** The current playback error, or null if this is not an error state. */
@@ -56,6 +59,10 @@
public final TrackSelectorResult trackSelectorResult;
/** The {@link MediaPeriodId} of the currently loading media period in the {@link #timeline}. */
public final MediaPeriodId loadingMediaPeriodId;
+ /** Whether playback should proceed when {@link #playbackState} == {@link Player#STATE_READY}. */
+ public final boolean playWhenReady;
+ /** Reason why playback is suppressed even though {@link #playWhenReady} is {@code true}. */
+ @PlaybackSuppressionReason public final int playbackSuppressionReason;
/**
* Position up to which media is buffered in {@link #loadingMediaPeriodId) relative to the start
@@ -85,13 +92,15 @@
return new PlaybackInfo(
Timeline.EMPTY,
DUMMY_MEDIA_PERIOD_ID,
- /* contentPositionUs= */ C.TIME_UNSET,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
Player.STATE_IDLE,
/* playbackError= */ null,
/* isLoading= */ false,
TrackGroupArray.EMPTY,
emptyTrackSelectorResult,
DUMMY_MEDIA_PERIOD_ID,
+ /* playWhenReady= */ false,
+ Player.PLAYBACK_SUPPRESSION_REASON_NONE,
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0);
@@ -102,7 +111,7 @@
*
* @param timeline See {@link #timeline}.
* @param periodId See {@link #periodId}.
- * @param contentPositionUs See {@link #contentPositionUs}.
+ * @param requestedContentPositionUs See {@link #requestedContentPositionUs}.
* @param playbackState See {@link #playbackState}.
* @param isLoading See {@link #isLoading}.
* @param trackGroups See {@link #trackGroups}.
@@ -115,25 +124,29 @@
public PlaybackInfo(
Timeline timeline,
MediaPeriodId periodId,
- long contentPositionUs,
+ long requestedContentPositionUs,
@Player.State int playbackState,
@Nullable ExoPlaybackException playbackError,
boolean isLoading,
TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult,
MediaPeriodId loadingMediaPeriodId,
+ boolean playWhenReady,
+ @PlaybackSuppressionReason int playbackSuppressionReason,
long bufferedPositionUs,
long totalBufferedDurationUs,
long positionUs) {
this.timeline = timeline;
this.periodId = periodId;
- this.contentPositionUs = contentPositionUs;
+ this.requestedContentPositionUs = requestedContentPositionUs;
this.playbackState = playbackState;
this.playbackError = playbackError;
this.isLoading = isLoading;
this.trackGroups = trackGroups;
this.trackSelectorResult = trackSelectorResult;
this.loadingMediaPeriodId = loadingMediaPeriodId;
+ this.playWhenReady = playWhenReady;
+ this.playbackSuppressionReason = playbackSuppressionReason;
this.bufferedPositionUs = bufferedPositionUs;
this.totalBufferedDurationUs = totalBufferedDurationUs;
this.positionUs = positionUs;
@@ -149,8 +162,8 @@
*
* @param periodId New playing media period. See {@link #periodId}.
* @param positionUs New position. See {@link #positionUs}.
- * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored
- * if {@code periodId.isAd()} is true.
+ * @param requestedContentPositionUs New requested content position. See {@link
+ * #requestedContentPositionUs}.
* @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}.
* @param trackGroups The track groups for the new position. See {@link #trackGroups}.
* @param trackSelectorResult The track selector result for the new position. See {@link
@@ -161,20 +174,22 @@
public PlaybackInfo copyWithNewPosition(
MediaPeriodId periodId,
long positionUs,
- long contentPositionUs,
+ long requestedContentPositionUs,
long totalBufferedDurationUs,
TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult) {
return new PlaybackInfo(
timeline,
periodId,
- periodId.isAd() ? contentPositionUs : C.TIME_UNSET,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
@@ -191,13 +206,15 @@
return new PlaybackInfo(
timeline,
periodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
@@ -214,13 +231,15 @@
return new PlaybackInfo(
timeline,
periodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
@@ -237,13 +256,15 @@
return new PlaybackInfo(
timeline,
periodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
@@ -260,13 +281,15 @@
return new PlaybackInfo(
timeline,
periodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
@@ -283,13 +306,44 @@
return new PlaybackInfo(
timeline,
periodId,
- contentPositionUs,
+ requestedContentPositionUs,
playbackState,
playbackError,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
+ bufferedPositionUs,
+ totalBufferedDurationUs,
+ positionUs);
+ }
+
+ /**
+ * Copies playback info with new information about whether playback should proceed when ready.
+ *
+ * @param playWhenReady Whether playback should proceed when {@link #playbackState} == {@link
+ * Player#STATE_READY}.
+ * @param playbackSuppressionReason Reason why playback is suppressed even though {@link
+ * #playWhenReady} is {@code true}.
+ * @return Copied playback info with new information.
+ */
+ @CheckResult
+ public PlaybackInfo copyWithPlayWhenReady(
+ boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
+ return new PlaybackInfo(
+ timeline,
+ periodId,
+ requestedContentPositionUs,
+ playbackState,
+ playbackError,
+ isLoading,
+ trackGroups,
+ trackSelectorResult,
+ loadingMediaPeriodId,
+ playWhenReady,
+ playbackSuppressionReason,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
index 057cb37..afa0a7e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
@@ -19,25 +19,19 @@
import com.google.android.exoplayer2.util.Assertions;
/**
- * The parameters that apply to playback.
+ * @deprecated Use {@link Player#setPlaybackSpeed(float)} and {@link
+ * Player.AudioComponent#setSkipSilenceEnabled(boolean)} instead.
*/
+@SuppressWarnings("deprecation")
+@Deprecated
public final class PlaybackParameters {
- /**
- * The default playback parameters: real-time playback with no pitch modification or silence
- * skipping.
- */
+ /** The default playback parameters: real-time playback with no silence skipping. */
public static final PlaybackParameters DEFAULT = new PlaybackParameters(/* speed= */ 1f);
/** The factor by which playback will be sped up. */
public final float speed;
- /** The factor by which the audio pitch will be scaled. */
- public final float pitch;
-
- /** Whether to skip silence in the input. */
- public final boolean skipSilence;
-
private final int scaledUsPerMs;
/**
@@ -46,33 +40,8 @@
* @param speed The factor by which playback will be sped up. Must be greater than zero.
*/
public PlaybackParameters(float speed) {
- this(speed, /* pitch= */ 1f, /* skipSilence= */ false);
- }
-
- /**
- * Creates new playback parameters that set the playback speed and audio pitch scaling factor.
- *
- * @param speed The factor by which playback will be sped up. Must be greater than zero.
- * @param pitch The factor by which the audio pitch will be scaled. Must be greater than zero.
- */
- public PlaybackParameters(float speed, float pitch) {
- this(speed, pitch, /* skipSilence= */ false);
- }
-
- /**
- * Creates new playback parameters that set the playback speed, audio pitch scaling factor and
- * whether to skip silence in the audio stream.
- *
- * @param speed The factor by which playback will be sped up. Must be greater than zero.
- * @param pitch The factor by which the audio pitch will be scaled. Must be greater than zero.
- * @param skipSilence Whether to skip silences in the audio stream.
- */
- public PlaybackParameters(float speed, float pitch, boolean skipSilence) {
Assertions.checkArgument(speed > 0);
- Assertions.checkArgument(pitch > 0);
this.speed = speed;
- this.pitch = pitch;
- this.skipSilence = skipSilence;
scaledUsPerMs = Math.round(speed * 1000f);
}
@@ -96,18 +65,11 @@
return false;
}
PlaybackParameters other = (PlaybackParameters) obj;
- return this.speed == other.speed
- && this.pitch == other.pitch
- && this.skipSilence == other.skipSilence;
+ return this.speed == other.speed;
}
@Override
public int hashCode() {
- int result = 17;
- result = 31 * result + Float.floatToRawIntBits(speed);
- result = 31 * result + Float.floatToRawIntBits(pitch);
- result = 31 * result + (skipSilence ? 1 : 0);
- return result;
+ return Float.floatToRawIntBits(speed);
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
index 1f1c23a..518e331 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
+import android.content.Context;
import android.os.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -26,6 +27,8 @@
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.device.DeviceListener;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.TextOutput;
@@ -38,6 +41,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* A media player interface defining traditional high-level functionality, such as the ability to
@@ -123,6 +127,18 @@
/** Returns the attributes for audio playback. */
AudioAttributes getAudioAttributes();
+ /**
+ * Sets the ID of the audio session to attach to the underlying {@link
+ * android.media.AudioTrack}.
+ *
+ * <p>The audio session ID can be generated using {@link C#generateAudioSessionIdV21(Context)}
+ * for API 21+.
+ *
+ * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} if it should
+ * be generated by the framework.
+ */
+ void setAudioSessionId(int audioSessionId);
+
/** Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set. */
int getAudioSessionId();
@@ -141,6 +157,16 @@
/** Returns the audio volume, with 0 being silence and 1 being unity gain. */
float getVolume();
+
+ /**
+ * Sets whether skipping silences in the audio stream is enabled.
+ *
+ * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled.
+ */
+ void setSkipSilenceEnabled(boolean skipSilenceEnabled);
+
+ /** Returns whether skipping silences in the audio stream is enabled. */
+ boolean getSkipSilenceEnabled();
}
/** The video component of a {@link Player}. */
@@ -343,6 +369,48 @@
void removeMetadataOutput(MetadataOutput output);
}
+ /** The device component of a {@link Player}. */
+ // Note: It's mostly from the androidx.media.VolumeProviderCompat and
+ // androidx.media.MediaControllerCompat.PlaybackInfo.
+ interface DeviceComponent {
+
+ /** Adds a listener to receive device events. */
+ void addDeviceListener(DeviceListener listener);
+
+ /** Removes a listener of device events. */
+ void removeDeviceListener(DeviceListener listener);
+
+ /** Gets the device information. */
+ DeviceInfo getDeviceInfo();
+
+ /**
+ * Gets the current volume of the device.
+ *
+ * <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume
+ * returned by this method varies according to the current {@link C.StreamType stream type}. The
+ * stream type is determined by {@link AudioAttributes#usage} which can be converted to stream
+ * type with {@link Util#getStreamTypeForAudioUsage(int)}. The audio attributes can be set to
+ * the player by calling {@link AudioComponent#setAudioAttributes}.
+ *
+ * <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of
+ * the remote device is returned.
+ */
+ int getDeviceVolume();
+
+ /**
+ * Sets the volume of the device.
+ *
+ * @param volume The volume to set.
+ */
+ void setDeviceVolume(int volume);
+
+ /** Increases the volume of the device. */
+ void increaseDeviceVolume();
+
+ /** Decreases the volume of the device. */
+ void decreaseDeviceVolume();
+ }
+
/**
* Listener of changes in player state. All methods have no-op default implementations to allow
* selective overrides.
@@ -407,16 +475,37 @@
*
* @param isLoading Whether the source is currently being loaded.
*/
+ @SuppressWarnings("deprecation")
+ default void onIsLoadingChanged(boolean isLoading) {
+ onLoadingChanged(isLoading);
+ }
+
+ /** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */
+ @Deprecated
default void onLoadingChanged(boolean isLoading) {}
/**
- * Called when the value returned from either {@link #getPlayWhenReady()} or {@link
- * #getPlaybackState()} changes.
+ * @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link
+ * #onPlayWhenReadyChanged(boolean, int)} instead.
+ */
+ @Deprecated
+ default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {}
+
+ /**
+ * Called when the value returned from {@link #getPlaybackState()} changes.
+ *
+ * @param state The new playback {@link State state}.
+ */
+ default void onPlaybackStateChanged(@State int state) {}
+
+ /**
+ * Called when the value returned from {@link #getPlayWhenReady()} changes.
*
* @param playWhenReady Whether playback will proceed when ready.
- * @param playbackState The new {@link State playback state}.
+ * @param reason The {@link PlayWhenReadyChangeReason reason} for the change.
*/
- default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {}
+ default void onPlayWhenReadyChanged(
+ boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {}
/**
* Called when the value returned from {@link #getPlaybackSuppressionReason()} changes.
@@ -471,19 +560,25 @@
default void onPositionDiscontinuity(@DiscontinuityReason int reason) {}
/**
- * Called when the current playback parameters change. The playback parameters may change due to
- * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change
- * them (for example, if audio playback switches to passthrough mode, where speed adjustment is
- * no longer possible).
- *
- * @param playbackParameters The playback parameters.
+ * @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
+ * AudioListener#onSkipSilenceEnabledChanged(boolean)} instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
/**
+ * Called when the current playback speed changes. The normal playback speed is 1. The speed may
+ * change due to a call to {@link #setPlaybackSpeed(float)}, or the player itself may change it
+ * (for example, if audio playback switches to passthrough mode, where speed adjustment is no
+ * longer possible).
+ */
+ default void onPlaybackSpeedChanged(float playbackSpeed) {}
+
+ /**
* Called when all pending seek requests have been processed by the player. This is guaranteed
* to happen after any necessary changes to the player state were reported to {@link
- * #onPlayerStateChanged(boolean, int)}.
+ * #onPlaybackStateChanged(int)}.
*/
default void onSeekProcessed() {}
}
@@ -550,6 +645,35 @@
int STATE_ENDED = 4;
/**
+ * Reasons for {@link #getPlayWhenReady() playWhenReady} changes. One of {@link
+ * #PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST}, {@link
+ * #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS}, {@link
+ * #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY}, {@link
+ * #PLAY_WHEN_READY_CHANGE_REASON_REMOTE} or {@link
+ * #PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
+ PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
+ PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY,
+ PLAY_WHEN_READY_CHANGE_REASON_REMOTE,
+ PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM
+ })
+ @interface PlayWhenReadyChangeReason {}
+ /** Playback has been started or paused by a call to {@link #setPlayWhenReady(boolean)}. */
+ int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1;
+ /** Playback has been paused because of a loss of audio focus. */
+ int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2;
+ /** Playback has been paused to avoid becoming noisy. */
+ int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3;
+ /** Playback has been started or paused because of a remote change. */
+ int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4;
+ /** Playback has been paused at the end of a media item. */
+ int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5;
+
+ /**
* Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
* of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link
* #PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}.
@@ -632,6 +756,9 @@
/** Timeline changed as a result of a dynamic update introduced by the played media. */
int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1;
+ /** The default playback speed. */
+ float DEFAULT_PLAYBACK_SPEED = 1.0f;
+
/** Returns the component of this player for audio output, or null if audio is not supported. */
@Nullable
AudioComponent getAudioComponent();
@@ -650,6 +777,10 @@
@Nullable
MetadataComponent getMetadataComponent();
+ /** Returns the component of this player for playback device, or null if it's not supported. */
+ @Nullable
+ DeviceComponent getDeviceComponent();
+
/**
* Returns the {@link Looper} associated with the application thread that's used to access the
* player and on which player events are received.
@@ -673,6 +804,134 @@
void removeListener(EventListener listener);
/**
+ * Clears the playlist, adds the specified {@link MediaItem MediaItems} and resets the position to
+ * the default position.
+ *
+ * @param mediaItems The new {@link MediaItem MediaItems}.
+ */
+ void setMediaItems(List<MediaItem> mediaItems);
+
+ /**
+ * Clears the playlist and adds the specified {@link MediaItem MediaItems}.
+ *
+ * @param mediaItems The new {@link MediaItem MediaItems}.
+ * @param resetPosition Whether the playback position should be reset to the default position in
+ * the first {@link Timeline.Window}. If false, playback will start from the position defined
+ * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}.
+ */
+ void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition);
+
+ /**
+ * Clears the playlist and adds the specified {@link MediaItem MediaItems}.
+ *
+ * @param mediaItems The new {@link MediaItem MediaItems}.
+ * @param startWindowIndex The window index to start playback from. If {@link C#INDEX_UNSET} is
+ * passed, the current position is not reset.
+ * @param startPositionMs The position in milliseconds to start playback from. If {@link
+ * C#TIME_UNSET} is passed, the default position of the given window is used. In any case, if
+ * {@code startWindowIndex} is set to {@link C#INDEX_UNSET}, this parameter is ignored and the
+ * position is not reset at all.
+ */
+ void setMediaItems(List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs);
+
+ /**
+ * Clears the playlist, adds the specified {@link MediaItem} and resets the position to the
+ * default position.
+ *
+ * @param mediaItem The new {@link MediaItem}.
+ */
+ void setMediaItem(MediaItem mediaItem);
+
+ /**
+ * Clears the playlist and adds the specified {@link MediaItem}.
+ *
+ * @param mediaItem The new {@link MediaItem}.
+ * @param startPositionMs The position in milliseconds to start playback from.
+ */
+ void setMediaItem(MediaItem mediaItem, long startPositionMs);
+
+ /**
+ * Clears the playlist and adds the specified {@link MediaItem}.
+ *
+ * @param mediaItem The new {@link MediaItem}.
+ * @param resetPosition Whether the playback position should be reset to the default position. If
+ * false, playback will start from the position defined by {@link #getCurrentWindowIndex()}
+ * and {@link #getCurrentPosition()}.
+ */
+ void setMediaItem(MediaItem mediaItem, boolean resetPosition);
+
+ /**
+ * Adds a media item to the end of the playlist.
+ *
+ * @param mediaItem The {@link MediaItem} to add.
+ */
+ void addMediaItem(MediaItem mediaItem);
+
+ /**
+ * Adds a media item at the given index of the playlist.
+ *
+ * @param index The index at which to add the item.
+ * @param mediaItem The {@link MediaItem} to add.
+ */
+ void addMediaItem(int index, MediaItem mediaItem);
+
+ /**
+ * Adds a list of media items to the end of the playlist.
+ *
+ * @param mediaItems The {@link MediaItem MediaItems} to add.
+ */
+ void addMediaItems(List<MediaItem> mediaItems);
+
+ /**
+ * Adds a list of media items at the given index of the playlist.
+ *
+ * @param index The index at which to add the media items.
+ * @param mediaItems The {@link MediaItem MediaItems} to add.
+ */
+ void addMediaItems(int index, List<MediaItem> mediaItems);
+
+ /**
+ * Moves the media item at the current index to the new index.
+ *
+ * @param currentIndex The current index of the media item to move.
+ * @param newIndex The new index of the media item. If the new index is larger than the size of
+ * the playlist the item is moved to the end of the playlist.
+ */
+ void moveMediaItem(int currentIndex, int newIndex);
+
+ /**
+ * Moves the media item range to the new index.
+ *
+ * @param fromIndex The start of the range to move.
+ * @param toIndex The first item not to be included in the range (exclusive).
+ * @param newIndex The new index of the first media item of the range. If the new index is larger
+ * than the size of the remaining playlist after removing the range, the range is moved to the
+ * end of the playlist.
+ */
+ void moveMediaItems(int fromIndex, int toIndex, int newIndex);
+
+ /**
+ * Removes the media item at the given index of the playlist.
+ *
+ * @param index The index at which to remove the media item.
+ */
+ void removeMediaItem(int index);
+
+ /**
+ * Removes a range of media items from the playlist.
+ *
+ * @param fromIndex The index at which to start removing media items.
+ * @param toIndex The index of the first item to be kept (exclusive).
+ */
+ void removeMediaItems(int fromIndex, int toIndex);
+
+ /** Clears the playlist. */
+ void clearMediaItems();
+
+ /** Prepares the player. */
+ void prepare();
+
+ /**
* Returns the current {@link State playback state} of the player.
*
* @return The current {@link State playback state}.
@@ -716,6 +975,11 @@
* @return The error, or {@code null}.
*/
@Nullable
+ ExoPlaybackException getPlayerError();
+
+ /** @deprecated Use {@link #getPlayerError()} instead. */
+ @Deprecated
+ @Nullable
ExoPlaybackException getPlaybackError();
/**
@@ -839,25 +1103,40 @@
void next();
/**
- * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
- * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
- *
- * <p>Playback parameters changes may cause the player to buffer. {@link
- * EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the
- * currently active playback parameters change.
- *
- * @param playbackParameters The playback parameters, or {@code null} to use the defaults.
+ * @deprecated Use {@link #setPlaybackSpeed(float)} or {@link
+ * AudioComponent#setSkipSilenceEnabled(boolean)} instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
/**
- * Returns the currently active playback parameters.
- *
- * @see EventListener#onPlaybackParametersChanged(PlaybackParameters)
+ * @deprecated Use {@link #getPlaybackSpeed()} or {@link AudioComponent#getSkipSilenceEnabled()}
+ * instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
PlaybackParameters getPlaybackParameters();
/**
+ * Attempts to set the playback speed.
+ *
+ * <p>Playback speed changes may cause the player to buffer. {@link
+ * EventListener#onPlaybackSpeedChanged(float)} will be called whenever the currently active
+ * playback speed change.
+ *
+ * @param playbackSpeed The playback speed.
+ */
+ void setPlaybackSpeed(float playbackSpeed);
+
+ /**
+ * Returns the currently active playback speed.
+ *
+ * @see EventListener#onPlaybackSpeedChanged(float)
+ */
+ float getPlaybackSpeed();
+
+ /**
* Stops playback without resetting the player. Use {@link #pause()} rather than this method if
* the intention is to pause playback.
*
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java
index e7ade7b..be7c7ce 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java
@@ -162,7 +162,9 @@
/**
* Returns position in window at {@link #getWindowIndex()} at which the message will be delivered,
- * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately.
+ * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. If {@link
+ * C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the window at {@link
+ * #getWindowIndex()}.
*/
public long getPositionMs() {
return positionMs;
@@ -172,7 +174,8 @@
* Sets a position in the current window at which the message will be delivered.
*
* @param positionMs The position in the current window at which the message will be sent, in
- * milliseconds.
+ * milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the message at the end of the
+ * current window.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
@@ -187,7 +190,8 @@
*
* @param windowIndex The index of the window at which the message will be sent.
* @param positionMs The position in the window with index {@code windowIndex} at which the
- * message will be sent, in milliseconds.
+ * message will be sent, in milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the
+ * message at the end of the window with index {@code windowIndex}.
* @return This message.
* @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not
* empty and the provided window index is not within the bounds of the timeline.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java
deleted file mode 100644
index b11b454..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/Playlist.java
+++ /dev/null
@@ -1,709 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2;
-
-import android.os.Handler;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.analytics.AnalyticsCollector;
-import com.google.android.exoplayer2.source.LoadEventInfo;
-import com.google.android.exoplayer2.source.MaskingMediaPeriod;
-import com.google.android.exoplayer2.source.MaskingMediaSource;
-import com.google.android.exoplayer2.source.MediaLoadData;
-import com.google.android.exoplayer2.source.MediaPeriod;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.MediaSourceEventListener;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
-import com.google.android.exoplayer2.upstream.Allocator;
-import com.google.android.exoplayer2.upstream.TransferListener;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Util;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
- * during playback. It is valid for the same {@link MediaSource} instance to be present more than
- * once in the playlist.
- *
- * <p>With the exception of the constructor, all methods are called on the playback thread.
- */
-/* package */ class Playlist {
-
- /** Listener for source events. */
- public interface PlaylistInfoRefreshListener {
-
- /**
- * Called when the timeline of a media item has changed and a new timeline that reflects the
- * current playlist state needs to be created by calling {@link #createTimeline()}.
- *
- * <p>Called on the playback thread.
- */
- void onPlaylistUpdateRequested();
- }
-
- private final List<MediaSourceHolder> mediaSourceHolders;
- private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
- private final Map<Object, MediaSourceHolder> mediaSourceByUid;
- private final PlaylistInfoRefreshListener playlistInfoListener;
- private final MediaSourceEventListener.EventDispatcher eventDispatcher;
- private final HashMap<Playlist.MediaSourceHolder, MediaSourceAndListener> childSources;
- private final Set<MediaSourceHolder> enabledMediaSourceHolders;
-
- private ShuffleOrder shuffleOrder;
- private boolean isPrepared;
-
- @Nullable private TransferListener mediaTransferListener;
-
- @SuppressWarnings("initialization")
- public Playlist(PlaylistInfoRefreshListener listener) {
- playlistInfoListener = listener;
- shuffleOrder = new DefaultShuffleOrder(0);
- mediaSourceByMediaPeriod = new IdentityHashMap<>();
- mediaSourceByUid = new HashMap<>();
- mediaSourceHolders = new ArrayList<>();
- eventDispatcher = new MediaSourceEventListener.EventDispatcher();
- childSources = new HashMap<>();
- enabledMediaSourceHolders = new HashSet<>();
- }
-
- /**
- * Sets the media sources replacing any sources previously contained in the playlist.
- *
- * @param holders The list of {@link MediaSourceHolder}s to set.
- * @param shuffleOrder The new shuffle order.
- * @return The new {@link Timeline}.
- */
- public final Timeline setMediaSources(
- List<MediaSourceHolder> holders, ShuffleOrder shuffleOrder) {
- removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ mediaSourceHolders.size());
- return addMediaSources(/* index= */ this.mediaSourceHolders.size(), holders, shuffleOrder);
- }
-
- /**
- * Adds multiple {@link MediaSourceHolder}s to the playlist.
- *
- * @param index The index at which the new {@link MediaSourceHolder}s will be inserted. This index
- * must be in the range of 0 <= index <= {@link #getSize()}.
- * @param holders A list of {@link MediaSourceHolder}s to be added.
- * @param shuffleOrder The new shuffle order.
- * @return The new {@link Timeline}.
- */
- public final Timeline addMediaSources(
- int index, List<MediaSourceHolder> holders, ShuffleOrder shuffleOrder) {
- if (!holders.isEmpty()) {
- this.shuffleOrder = shuffleOrder;
- for (int insertionIndex = index; insertionIndex < index + holders.size(); insertionIndex++) {
- MediaSourceHolder holder = holders.get(insertionIndex - index);
- if (insertionIndex > 0) {
- MediaSourceHolder previousHolder = mediaSourceHolders.get(insertionIndex - 1);
- Timeline previousTimeline = previousHolder.mediaSource.getTimeline();
- holder.reset(
- /* firstWindowIndexInChild= */ previousHolder.firstWindowIndexInChild
- + previousTimeline.getWindowCount());
- } else {
- holder.reset(/* firstWindowIndexInChild= */ 0);
- }
- Timeline newTimeline = holder.mediaSource.getTimeline();
- correctOffsets(
- /* startIndex= */ insertionIndex,
- /* windowOffsetUpdate= */ newTimeline.getWindowCount());
- mediaSourceHolders.add(insertionIndex, holder);
- mediaSourceByUid.put(holder.uid, holder);
- if (isPrepared) {
- prepareChildSource(holder);
- if (mediaSourceByMediaPeriod.isEmpty()) {
- enabledMediaSourceHolders.add(holder);
- } else {
- disableChildSource(holder);
- }
- }
- }
- }
- return createTimeline();
- }
-
- /**
- * Removes a range of {@link MediaSourceHolder}s from the playlist, by specifying an initial index
- * (included) and a final index (excluded).
- *
- * <p>Note: when specified range is empty, no actual media source is removed and no exception is
- * thrown.
- *
- * @param fromIndex The initial range index, pointing to the first media source that will be
- * removed. This index must be in the range of 0 <= index <= {@link #getSize()}.
- * @param toIndex The final range index, pointing to the first media source that will be left
- * untouched. This index must be in the range of 0 <= index <= {@link #getSize()}.
- * @param shuffleOrder The new shuffle order.
- * @return The new {@link Timeline}.
- * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
- * {@code toIndex} > {@link #getSize()}, {@code fromIndex} > {@code toIndex}
- */
- public final Timeline removeMediaSourceRange(
- int fromIndex, int toIndex, ShuffleOrder shuffleOrder) {
- Assertions.checkArgument(fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize());
- this.shuffleOrder = shuffleOrder;
- removeMediaSourcesInternal(fromIndex, toIndex);
- return createTimeline();
- }
-
- /**
- * Moves an existing media source within the playlist.
- *
- * @param currentIndex The current index of the media source in the playlist. This index must be
- * in the range of 0 <= index < {@link #getSize()}.
- * @param newIndex The target index of the media source in the playlist. This index must be in the
- * range of 0 <= index < {@link #getSize()}.
- * @param shuffleOrder The new shuffle order.
- * @return The new {@link Timeline}.
- * @throws IllegalArgumentException When an index is invalid, i.e. {@code currentIndex} < 0,
- * {@code currentIndex} >= {@link #getSize()}, {@code newIndex} < 0
- */
- public final Timeline moveMediaSource(int currentIndex, int newIndex, ShuffleOrder shuffleOrder) {
- return moveMediaSourceRange(currentIndex, currentIndex + 1, newIndex, shuffleOrder);
- }
-
- /**
- * Moves a range of media sources within the playlist.
- *
- * <p>Note: when specified range is empty or the from index equals the new from index, no actual
- * media source is moved and no exception is thrown.
- *
- * @param fromIndex The initial range index, pointing to the first media source of the range that
- * will be moved. This index must be in the range of 0 <= index <= {@link #getSize()}.
- * @param toIndex The final range index, pointing to the first media source that will be left
- * untouched. This index must be larger or equals than {@code fromIndex}.
- * @param newFromIndex The target index of the first media source of the range that will be moved.
- * @param shuffleOrder The new shuffle order.
- * @return The new {@link Timeline}.
- * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
- * {@code toIndex} < {@code fromIndex}, {@code fromIndex} > {@code toIndex}, {@code
- * newFromIndex} < 0
- */
- public Timeline moveMediaSourceRange(
- int fromIndex, int toIndex, int newFromIndex, ShuffleOrder shuffleOrder) {
- Assertions.checkArgument(
- fromIndex >= 0 && fromIndex <= toIndex && toIndex <= getSize() && newFromIndex >= 0);
- this.shuffleOrder = shuffleOrder;
- if (fromIndex == toIndex || fromIndex == newFromIndex) {
- return createTimeline();
- }
- int startIndex = Math.min(fromIndex, newFromIndex);
- int newEndIndex = newFromIndex + (toIndex - fromIndex) - 1;
- int endIndex = Math.max(newEndIndex, toIndex - 1);
- int windowOffset = mediaSourceHolders.get(startIndex).firstWindowIndexInChild;
- moveMediaSourceHolders(mediaSourceHolders, fromIndex, toIndex, newFromIndex);
- for (int i = startIndex; i <= endIndex; i++) {
- MediaSourceHolder holder = mediaSourceHolders.get(i);
- holder.firstWindowIndexInChild = windowOffset;
- windowOffset += holder.mediaSource.getTimeline().getWindowCount();
- }
- return createTimeline();
- }
-
- /** Clears the playlist. */
- public final Timeline clear(@Nullable ShuffleOrder shuffleOrder) {
- this.shuffleOrder = shuffleOrder != null ? shuffleOrder : this.shuffleOrder.cloneAndClear();
- removeMediaSourcesInternal(/* fromIndex= */ 0, /* toIndex= */ getSize());
- return createTimeline();
- }
-
- /** Whether the playlist is prepared. */
- public final boolean isPrepared() {
- return isPrepared;
- }
-
- /** Returns the number of media sources in the playlist. */
- public final int getSize() {
- return mediaSourceHolders.size();
- }
-
- /**
- * Sets the {@link AnalyticsCollector}.
- *
- * @param handler The handler on which to call the collector.
- * @param analyticsCollector The analytics collector.
- */
- public final void setAnalyticsCollector(Handler handler, AnalyticsCollector analyticsCollector) {
- eventDispatcher.addEventListener(handler, analyticsCollector);
- }
-
- /**
- * Sets a new shuffle order to use when shuffling the child media sources.
- *
- * @param shuffleOrder A {@link ShuffleOrder}.
- */
- public final Timeline setShuffleOrder(ShuffleOrder shuffleOrder) {
- int size = getSize();
- if (shuffleOrder.getLength() != size) {
- shuffleOrder =
- shuffleOrder
- .cloneAndClear()
- .cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
- }
- this.shuffleOrder = shuffleOrder;
- return createTimeline();
- }
-
- /** Prepares the playlist. */
- public final void prepare(@Nullable TransferListener mediaTransferListener) {
- Assertions.checkState(!isPrepared);
- this.mediaTransferListener = mediaTransferListener;
- for (int i = 0; i < mediaSourceHolders.size(); i++) {
- MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
- prepareChildSource(mediaSourceHolder);
- enabledMediaSourceHolders.add(mediaSourceHolder);
- }
- isPrepared = true;
- }
-
- /**
- * Returns a new {@link MediaPeriod} identified by {@code periodId}.
- *
- * @param id The identifier of the period.
- * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
- * @param startPositionUs The expected start position, in microseconds.
- * @return A new {@link MediaPeriod}.
- */
- public MediaPeriod createPeriod(
- MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs) {
- Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
- MediaSource.MediaPeriodId childMediaPeriodId =
- id.copyWithPeriodUid(getChildPeriodUid(id.periodUid));
- MediaSourceHolder holder = Assertions.checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid));
- enableMediaSource(holder);
- holder.activeMediaPeriodIds.add(childMediaPeriodId);
- MediaPeriod mediaPeriod =
- holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
- mediaSourceByMediaPeriod.put(mediaPeriod, holder);
- disableUnusedMediaSources();
- return mediaPeriod;
- }
-
- /**
- * Releases the period.
- *
- * @param mediaPeriod The period to release.
- */
- public final void releasePeriod(MediaPeriod mediaPeriod) {
- MediaSourceHolder holder =
- Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
- holder.mediaSource.releasePeriod(mediaPeriod);
- holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
- if (!mediaSourceByMediaPeriod.isEmpty()) {
- disableUnusedMediaSources();
- }
- maybeReleaseChildSource(holder);
- }
-
- /** Releases the playlist. */
- public final void release() {
- for (MediaSourceAndListener childSource : childSources.values()) {
- childSource.mediaSource.releaseSource(childSource.caller);
- childSource.mediaSource.removeEventListener(childSource.eventListener);
- }
- childSources.clear();
- enabledMediaSourceHolders.clear();
- isPrepared = false;
- }
-
- /** Throws any pending error encountered while loading or refreshing. */
- public final void maybeThrowSourceInfoRefreshError() throws IOException {
- for (MediaSourceAndListener childSource : childSources.values()) {
- childSource.mediaSource.maybeThrowSourceInfoRefreshError();
- }
- }
-
- /** Creates a timeline reflecting the current state of the playlist. */
- public final Timeline createTimeline() {
- if (mediaSourceHolders.isEmpty()) {
- return Timeline.EMPTY;
- }
- int windowOffset = 0;
- for (int i = 0; i < mediaSourceHolders.size(); i++) {
- MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
- mediaSourceHolder.firstWindowIndexInChild = windowOffset;
- windowOffset += mediaSourceHolder.mediaSource.getTimeline().getWindowCount();
- }
- return new PlaylistTimeline(mediaSourceHolders, shuffleOrder);
- }
-
- // Internal methods.
-
- private void enableMediaSource(MediaSourceHolder mediaSourceHolder) {
- enabledMediaSourceHolders.add(mediaSourceHolder);
- @Nullable MediaSourceAndListener enabledChild = childSources.get(mediaSourceHolder);
- if (enabledChild != null) {
- enabledChild.mediaSource.enable(enabledChild.caller);
- }
- }
-
- private void disableUnusedMediaSources() {
- Iterator<MediaSourceHolder> iterator = enabledMediaSourceHolders.iterator();
- while (iterator.hasNext()) {
- MediaSourceHolder holder = iterator.next();
- if (holder.activeMediaPeriodIds.isEmpty()) {
- disableChildSource(holder);
- iterator.remove();
- }
- }
- }
-
- private void disableChildSource(MediaSourceHolder holder) {
- @Nullable MediaSourceAndListener disabledChild = childSources.get(holder);
- if (disabledChild != null) {
- disabledChild.mediaSource.disable(disabledChild.caller);
- }
- }
-
- private void removeMediaSourcesInternal(int fromIndex, int toIndex) {
- for (int index = toIndex - 1; index >= fromIndex; index--) {
- MediaSourceHolder holder = mediaSourceHolders.remove(index);
- mediaSourceByUid.remove(holder.uid);
- Timeline oldTimeline = holder.mediaSource.getTimeline();
- correctOffsets(
- /* startIndex= */ index, /* windowOffsetUpdate= */ -oldTimeline.getWindowCount());
- holder.isRemoved = true;
- if (isPrepared) {
- maybeReleaseChildSource(holder);
- }
- }
- }
-
- private void correctOffsets(int startIndex, int windowOffsetUpdate) {
- for (int i = startIndex; i < mediaSourceHolders.size(); i++) {
- MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i);
- mediaSourceHolder.firstWindowIndexInChild += windowOffsetUpdate;
- }
- }
-
- // Internal methods to manage child sources.
-
- @Nullable
- private static MediaSource.MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
- MediaSourceHolder mediaSourceHolder, MediaSource.MediaPeriodId mediaPeriodId) {
- for (int i = 0; i < mediaSourceHolder.activeMediaPeriodIds.size(); i++) {
- // Ensure the reported media period id has the same window sequence number as the one created
- // by this media source. Otherwise it does not belong to this child source.
- if (mediaSourceHolder.activeMediaPeriodIds.get(i).windowSequenceNumber
- == mediaPeriodId.windowSequenceNumber) {
- Object periodUid = getPeriodUid(mediaSourceHolder, mediaPeriodId.periodUid);
- return mediaPeriodId.copyWithPeriodUid(periodUid);
- }
- }
- return null;
- }
-
- private static int getWindowIndexForChildWindowIndex(
- MediaSourceHolder mediaSourceHolder, int windowIndex) {
- return windowIndex + mediaSourceHolder.firstWindowIndexInChild;
- }
-
- private void prepareChildSource(MediaSourceHolder holder) {
- MediaSource mediaSource = holder.mediaSource;
- MediaSource.MediaSourceCaller caller =
- (source, timeline) -> playlistInfoListener.onPlaylistUpdateRequested();
- MediaSourceEventListener eventListener = new ForwardingEventListener(holder);
- childSources.put(holder, new MediaSourceAndListener(mediaSource, caller, eventListener));
- mediaSource.addEventListener(Util.createHandler(), eventListener);
- mediaSource.prepareSource(caller, mediaTransferListener);
- }
-
- private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
- // Release if the source has been removed from the playlist and no periods are still active.
- if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
- MediaSourceAndListener removedChild =
- Assertions.checkNotNull(childSources.remove(mediaSourceHolder));
- removedChild.mediaSource.releaseSource(removedChild.caller);
- removedChild.mediaSource.removeEventListener(removedChild.eventListener);
- enabledMediaSourceHolders.remove(mediaSourceHolder);
- }
- }
-
- /** Return uid of media source holder from period uid of concatenated source. */
- private static Object getMediaSourceHolderUid(Object periodUid) {
- return PlaylistTimeline.getChildTimelineUidFromConcatenatedUid(periodUid);
- }
-
- /** Return uid of child period from period uid of concatenated source. */
- private static Object getChildPeriodUid(Object periodUid) {
- return PlaylistTimeline.getChildPeriodUidFromConcatenatedUid(periodUid);
- }
-
- private static Object getPeriodUid(MediaSourceHolder holder, Object childPeriodUid) {
- return PlaylistTimeline.getConcatenatedUid(holder.uid, childPeriodUid);
- }
-
- /* package */ static void moveMediaSourceHolders(
- List<MediaSourceHolder> mediaSourceHolders, int fromIndex, int toIndex, int newFromIndex) {
- MediaSourceHolder[] removedItems = new MediaSourceHolder[toIndex - fromIndex];
- for (int i = removedItems.length - 1; i >= 0; i--) {
- removedItems[i] = mediaSourceHolders.remove(fromIndex + i);
- }
- mediaSourceHolders.addAll(
- Math.min(newFromIndex, mediaSourceHolders.size()), Arrays.asList(removedItems));
- }
-
- /** Data class to hold playlist media sources together with meta data needed to process them. */
- /* package */ static final class MediaSourceHolder {
-
- public final MaskingMediaSource mediaSource;
- public final Object uid;
- public final List<MediaSource.MediaPeriodId> activeMediaPeriodIds;
-
- public int firstWindowIndexInChild;
- public boolean isRemoved;
-
- public MediaSourceHolder(MediaSource mediaSource, boolean useLazyPreparation) {
- this.mediaSource = new MaskingMediaSource(mediaSource, useLazyPreparation);
- this.activeMediaPeriodIds = new ArrayList<>();
- this.uid = new Object();
- }
-
- public void reset(int firstWindowIndexInChild) {
- this.firstWindowIndexInChild = firstWindowIndexInChild;
- this.isRemoved = false;
- this.activeMediaPeriodIds.clear();
- }
- }
-
- /** Timeline exposing concatenated timelines of playlist media sources. */
- /* package */ static final class PlaylistTimeline extends AbstractConcatenatedTimeline {
-
- private final int windowCount;
- private final int periodCount;
- private final int[] firstPeriodInChildIndices;
- private final int[] firstWindowInChildIndices;
- private final Timeline[] timelines;
- private final Object[] uids;
- private final HashMap<Object, Integer> childIndexByUid;
-
- public PlaylistTimeline(
- Collection<MediaSourceHolder> mediaSourceHolders, ShuffleOrder shuffleOrder) {
- super(/* isAtomic= */ false, shuffleOrder);
- int childCount = mediaSourceHolders.size();
- firstPeriodInChildIndices = new int[childCount];
- firstWindowInChildIndices = new int[childCount];
- timelines = new Timeline[childCount];
- uids = new Object[childCount];
- childIndexByUid = new HashMap<>();
- int index = 0;
- int windowCount = 0;
- int periodCount = 0;
- for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
- timelines[index] = mediaSourceHolder.mediaSource.getTimeline();
- firstWindowInChildIndices[index] = windowCount;
- firstPeriodInChildIndices[index] = periodCount;
- windowCount += timelines[index].getWindowCount();
- periodCount += timelines[index].getPeriodCount();
- uids[index] = mediaSourceHolder.uid;
- childIndexByUid.put(uids[index], index++);
- }
- this.windowCount = windowCount;
- this.periodCount = periodCount;
- }
-
- @Override
- protected int getChildIndexByPeriodIndex(int periodIndex) {
- return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
- }
-
- @Override
- protected int getChildIndexByWindowIndex(int windowIndex) {
- return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
- }
-
- @Override
- protected int getChildIndexByChildUid(Object childUid) {
- Integer index = childIndexByUid.get(childUid);
- return index == null ? C.INDEX_UNSET : index;
- }
-
- @Override
- protected Timeline getTimelineByChildIndex(int childIndex) {
- return timelines[childIndex];
- }
-
- @Override
- protected int getFirstPeriodIndexByChildIndex(int childIndex) {
- return firstPeriodInChildIndices[childIndex];
- }
-
- @Override
- protected int getFirstWindowIndexByChildIndex(int childIndex) {
- return firstWindowInChildIndices[childIndex];
- }
-
- @Override
- protected Object getChildUidByChildIndex(int childIndex) {
- return uids[childIndex];
- }
-
- @Override
- public int getWindowCount() {
- return windowCount;
- }
-
- @Override
- public int getPeriodCount() {
- return periodCount;
- }
- }
-
- private static final class MediaSourceAndListener {
-
- public final MediaSource mediaSource;
- public final MediaSource.MediaSourceCaller caller;
- public final MediaSourceEventListener eventListener;
-
- public MediaSourceAndListener(
- MediaSource mediaSource,
- MediaSource.MediaSourceCaller caller,
- MediaSourceEventListener eventListener) {
- this.mediaSource = mediaSource;
- this.caller = caller;
- this.eventListener = eventListener;
- }
- }
-
- private final class ForwardingEventListener implements MediaSourceEventListener {
-
- private final Playlist.MediaSourceHolder id;
- private EventDispatcher eventDispatcher;
-
- public ForwardingEventListener(Playlist.MediaSourceHolder id) {
- eventDispatcher = Playlist.this.eventDispatcher;
- this.id = id;
- }
-
- @Override
- public void onMediaPeriodCreated(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.mediaPeriodCreated();
- }
- }
-
- @Override
- public void onMediaPeriodReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.mediaPeriodReleased();
- }
- }
-
- @Override
- public void onLoadStarted(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventData,
- MediaLoadData mediaLoadData) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.loadStarted(loadEventData, mediaLoadData);
- }
- }
-
- @Override
- public void onLoadCompleted(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventData,
- MediaLoadData mediaLoadData) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.loadCompleted(loadEventData, mediaLoadData);
- }
- }
-
- @Override
- public void onLoadCanceled(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventData,
- MediaLoadData mediaLoadData) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.loadCanceled(loadEventData, mediaLoadData);
- }
- }
-
- @Override
- public void onLoadError(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- LoadEventInfo loadEventData,
- MediaLoadData mediaLoadData,
- IOException error,
- boolean wasCanceled) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.loadError(loadEventData, mediaLoadData, error, wasCanceled);
- }
- }
-
- @Override
- public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.readingStarted();
- }
- }
-
- @Override
- public void onUpstreamDiscarded(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- MediaLoadData mediaLoadData) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.upstreamDiscarded(mediaLoadData);
- }
- }
-
- @Override
- public void onDownstreamFormatChanged(
- int windowIndex,
- @Nullable MediaSource.MediaPeriodId mediaPeriodId,
- MediaLoadData mediaLoadData) {
- if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
- eventDispatcher.downstreamFormatChanged(mediaLoadData);
- }
- }
-
- /** Updates the event dispatcher and returns whether the event should be dispatched. */
- private boolean maybeUpdateEventDispatcher(
- int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) {
- @Nullable MediaSource.MediaPeriodId mediaPeriodId = null;
- if (childMediaPeriodId != null) {
- mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId);
- if (mediaPeriodId == null) {
- // Media period not found. Ignore event.
- return false;
- }
- }
- int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex);
- if (eventDispatcher.windowIndex != windowIndex
- || !Util.areEqual(eventDispatcher.mediaPeriodId, mediaPeriodId)) {
- eventDispatcher =
- Playlist.this.eventDispatcher.withParameters(
- windowIndex, mediaPeriodId, /* mediaTimeOffsetMs= */ 0L);
- }
- return true;
- }
- }
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java
index 9d6dbb5..2170606 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java
@@ -24,7 +24,7 @@
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.Util;
-import com.google.android.exoplayer2.video.SimpleDecoderVideoRenderer;
+import com.google.android.exoplayer2.video.DecoderVideoRenderer;
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
@@ -115,7 +115,7 @@
@SuppressWarnings("deprecation")
int MSG_SET_CAMERA_MOTION_LISTENER = C.MSG_SET_CAMERA_MOTION_LISTENER;
/**
- * The type of a message that can be passed to a {@link SimpleDecoderVideoRenderer} via {@link
+ * The type of a message that can be passed to a {@link DecoderVideoRenderer} via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be the target {@link
* VideoDecoderOutputBufferRenderer}, or null.
*
@@ -126,6 +126,18 @@
@SuppressWarnings("deprecation")
int MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER = C.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER;
/**
+ * The type of a message that can be passed to an audio renderer via {@link
+ * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Boolean} instance
+ * telling whether to enable or disable skipping silences in the audio stream.
+ */
+ int MSG_SET_SKIP_SILENCE_ENABLED = 101;
+ /**
+ * A type of a message that can be passed to an audio renderer via {@link
+ * ExoPlayer#createMessage(Target)}. The message payload should be an {@link Integer} instance
+ * representing the audio session ID that will be attached to the underlying audio track.
+ */
+ int MSG_SET_AUDIO_SESSION_ID = 102;
+ /**
* Applications or extensions may define custom {@code MSG_*} constants that can be passed to
* renderers. These custom constants must be greater than or equal to this value.
*/
@@ -178,6 +190,14 @@
int STATE_STARTED = 2;
/**
+ * Returns the name of this renderer, for logging and debugging purposes. Should typically be the
+ * renderer's (un-obfuscated) class name.
+ *
+ * @return The name of this renderer.
+ */
+ String getName();
+
+ /**
* Returns the track type that the renderer handles. For example, a video renderer will return
* {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a text
* renderer will return {@link C#TRACK_TYPE_TEXT}, and so on.
@@ -222,21 +242,30 @@
/**
* Enables the renderer to consume from the specified {@link SampleStream}.
- * <p>
- * This method may be called when the renderer is in the following states:
- * {@link #STATE_DISABLED}.
+ *
+ * <p>This method may be called when the renderer is in the following states: {@link
+ * #STATE_DISABLED}.
*
* @param configuration The renderer configuration.
* @param formats The enabled formats.
* @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback.
- * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream}
- * before they are rendered.
+ * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
+ * stream even if the state is not {@link #STATE_STARTED} yet.
+ * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before
+ * they are rendered.
* @throws ExoPlaybackException If an error occurs.
*/
- void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream,
- long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;
+ void enable(
+ RendererConfiguration configuration,
+ Format[] formats,
+ SampleStream stream,
+ long positionUs,
+ boolean joining,
+ boolean mayRenderStartOfStream,
+ long offsetUs)
+ throws ExoPlaybackException;
/**
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be
@@ -341,21 +370,26 @@
/**
* Incrementally renders the {@link SampleStream}.
- * <p>
- * If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do
- * work toward being ready to render the {@link SampleStream} when the renderer is started. It may
- * also render the very start of the media, for example the first frame of a video stream. If the
+ *
+ * <p>If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do
+ * work toward being ready to render the {@link SampleStream} when the renderer is started. If the
* renderer is in the {@link #STATE_STARTED} state then calls to this method will render the
* {@link SampleStream} in sync with the specified media positions.
- * <p>
- * This method should return quickly, and should not block if the renderer is unable to make
- * useful progress.
- * <p>
- * This method may be called when the renderer is in the following states:
- * {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
- * @param positionUs The current media time in microseconds, measured at the start of the
- * current iteration of the rendering loop.
+ * <p>The renderer may also render the very start of the media at the current position (e.g. the
+ * first frame of a video stream) while still in the {@link #STATE_ENABLED} state, unless it's the
+ * initial start of the media after calling {@link #enable(RendererConfiguration, Format[],
+ * SampleStream, long, boolean, boolean, long)} with {@code mayRenderStartOfStream} set to {@code
+ * false}.
+ *
+ * <p>This method should return quickly, and should not block if the renderer is unable to make
+ * useful progress.
+ *
+ * <p>This method may be called when the renderer is in the following states: {@link
+ * #STATE_ENABLED}, {@link #STATE_STARTED}.
+ *
+ * @param positionUs The current media time in microseconds, measured at the start of the current
+ * iteration of the rendering loop.
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
* measured at the start of the current iteration of the rendering loop.
* @throws ExoPlaybackException If an error occurs.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java
index a757652..882c0d1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java
@@ -260,6 +260,9 @@
}
}
+ /** Returns the name of the {@link Renderer}. */
+ String getName();
+
/**
* Returns the track type that the {@link Renderer} handles. For example, a video renderer will
* return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java
index 6f0d125..74ee923 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java
@@ -16,10 +16,7 @@
package com.google.android.exoplayer2;
import android.os.Handler;
-import androidx.annotation.Nullable;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
@@ -37,7 +34,6 @@
* @param audioRendererEventListener An event listener for audio renderers.
* @param textRendererOutput An output for text renderers.
* @param metadataRendererOutput An output for metadata renderers.
- * @param drmSessionManager A drm session manager used by renderers.
* @return The {@link Renderer instances}.
*/
Renderer[] createRenderers(
@@ -45,6 +41,5 @@
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
- MetadataOutput metadataRendererOutput,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager);
+ MetadataOutput metadataRendererOutput);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
index ef7e715..5d98e59 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2;
-import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -28,6 +27,7 @@
import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
@@ -36,12 +36,13 @@
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.decoder.DecoderCounters;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
+import com.google.android.exoplayer2.device.DeviceInfo;
+import com.google.android.exoplayer2.device.DeviceListener;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
@@ -74,7 +75,8 @@
Player.AudioComponent,
Player.VideoComponent,
Player.TextComponent,
- Player.MetadataComponent {
+ Player.MetadataComponent,
+ Player.DeviceComponent {
/** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */
@Deprecated
@@ -92,6 +94,7 @@
private Clock clock;
private TrackSelector trackSelector;
+ private MediaSourceFactory mediaSourceFactory;
private LoadControl loadControl;
private BandwidthMeter bandwidthMeter;
private AnalyticsCollector analyticsCollector;
@@ -111,6 +114,7 @@
* <ul>
* <li>{@link RenderersFactory}: {@link DefaultRenderersFactory}
* <li>{@link TrackSelector}: {@link DefaultTrackSelector}
+ * <li>{@link MediaSourceFactory}: {@link DefaultMediaSourceFactory}
* <li>{@link LoadControl}: {@link DefaultLoadControl}
* <li>{@link BandwidthMeter}: {@link DefaultBandwidthMeter#getSingletonInstance(Context)}
* <li>{@link Looper}: The {@link Looper} associated with the current thread, or the {@link
@@ -141,6 +145,7 @@
context,
renderersFactory,
new DefaultTrackSelector(context),
+ DefaultMediaSourceFactory.newInstance(context),
new DefaultLoadControl(),
DefaultBandwidthMeter.getSingletonInstance(context),
Util.getLooper(),
@@ -160,6 +165,7 @@
* @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the
* player.
* @param trackSelector A {@link TrackSelector}.
+ * @param mediaSourceFactory A {@link MediaSourceFactory}.
* @param loadControl A {@link LoadControl}.
* @param bandwidthMeter A {@link BandwidthMeter}.
* @param looper A {@link Looper} that must be used for all calls to the player.
@@ -173,6 +179,7 @@
Context context,
RenderersFactory renderersFactory,
TrackSelector trackSelector,
+ MediaSourceFactory mediaSourceFactory,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
Looper looper,
@@ -182,6 +189,7 @@
this.context = context;
this.renderersFactory = renderersFactory;
this.trackSelector = trackSelector;
+ this.mediaSourceFactory = mediaSourceFactory;
this.loadControl = loadControl;
this.bandwidthMeter = bandwidthMeter;
this.looper = looper;
@@ -204,6 +212,19 @@
}
/**
+ * Sets the {@link MediaSourceFactory} that will be used by the player.
+ *
+ * @param mediaSourceFactory A {@link MediaSourceFactory}.
+ * @return This builder.
+ * @throws IllegalStateException If {@link #build()} has already been called.
+ */
+ public Builder setMediaSourceFactory(MediaSourceFactory mediaSourceFactory) {
+ Assertions.checkState(!buildCalled);
+ this.mediaSourceFactory = mediaSourceFactory;
+ return this;
+ }
+
+ /**
* Sets the {@link LoadControl} that will be used by the player.
*
* @param loadControl A {@link LoadControl}.
@@ -296,16 +317,7 @@
public SimpleExoPlayer build() {
Assertions.checkState(!buildCalled);
buildCalled = true;
- return new SimpleExoPlayer(
- context,
- renderersFactory,
- trackSelector,
- loadControl,
- bandwidthMeter,
- analyticsCollector,
- useLazyPreparation,
- clock,
- looper);
+ return new SimpleExoPlayer(/* builder= */ this);
}
}
@@ -321,6 +333,7 @@
private final CopyOnWriteArraySet<AudioListener> audioListeners;
private final CopyOnWriteArraySet<TextOutput> textOutputs;
private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs;
+ private final CopyOnWriteArraySet<DeviceListener> deviceListeners;
private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners;
private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners;
private final BandwidthMeter bandwidthMeter;
@@ -328,7 +341,9 @@
private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager;
+ private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager;
+ private final WifiLockManager wifiLockManager;
@Nullable private Format videoFormat;
@Nullable private Format audioFormat;
@@ -346,6 +361,7 @@
private int audioSessionId;
private AudioAttributes audioAttributes;
private float audioVolume;
+ private boolean skipSilenceEnabled;
private List<Cue> currentCues;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
@@ -353,6 +369,22 @@
@Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased;
+ private DeviceInfo deviceInfo;
+
+ /** @param builder The {@link Builder} to obtain all construction parameters. */
+ protected SimpleExoPlayer(Builder builder) {
+ this(
+ builder.context,
+ builder.renderersFactory,
+ builder.trackSelector,
+ builder.mediaSourceFactory,
+ builder.loadControl,
+ builder.bandwidthMeter,
+ builder.analyticsCollector,
+ builder.useLazyPreparation,
+ builder.clock,
+ builder.looper);
+ }
/**
* @param context A {@link Context}.
@@ -370,47 +402,17 @@
* @param looper The {@link Looper} which must be used for all calls to the player and which is
* used to call listeners on.
*/
- @SuppressWarnings("deprecation")
protected SimpleExoPlayer(
Context context,
RenderersFactory renderersFactory,
TrackSelector trackSelector,
+ MediaSourceFactory mediaSourceFactory,
LoadControl loadControl,
BandwidthMeter bandwidthMeter,
AnalyticsCollector analyticsCollector,
boolean useLazyPreparation,
Clock clock,
Looper looper) {
- this(
- context,
- renderersFactory,
- trackSelector,
- loadControl,
- DrmSessionManager.getDummyDrmSessionManager(),
- bandwidthMeter,
- analyticsCollector,
- useLazyPreparation,
- clock,
- looper);
- }
-
- /**
- * @deprecated Use {@link #SimpleExoPlayer(Context, RenderersFactory, TrackSelector, LoadControl,
- * BandwidthMeter, AnalyticsCollector, boolean, Clock, Looper)} instead, and pass the {@link
- * DrmSessionManager} to the {@link MediaSource} factories.
- */
- @Deprecated
- protected SimpleExoPlayer(
- Context context,
- RenderersFactory renderersFactory,
- TrackSelector trackSelector,
- LoadControl loadControl,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- BandwidthMeter bandwidthMeter,
- AnalyticsCollector analyticsCollector,
- boolean useLazyPreparation,
- Clock clock,
- Looper looper) {
this.bandwidthMeter = bandwidthMeter;
this.analyticsCollector = analyticsCollector;
componentListener = new ComponentListener();
@@ -418,6 +420,7 @@
audioListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>();
metadataOutputs = new CopyOnWriteArraySet<>();
+ deviceListeners = new CopyOnWriteArraySet<>();
videoDebugListeners = new CopyOnWriteArraySet<>();
audioDebugListeners = new CopyOnWriteArraySet<>();
eventHandler = new Handler(looper);
@@ -427,8 +430,7 @@
componentListener,
componentListener,
componentListener,
- componentListener,
- drmSessionManager);
+ componentListener);
// Set initial values.
audioVolume = 1;
@@ -442,6 +444,7 @@
new ExoPlayerImpl(
renderers,
trackSelector,
+ mediaSourceFactory,
loadControl,
bandwidthMeter,
analyticsCollector,
@@ -449,21 +452,21 @@
clock,
looper);
analyticsCollector.setPlayer(player);
- addListener(analyticsCollector);
- addListener(componentListener);
+ player.addListener(analyticsCollector);
+ player.addListener(componentListener);
videoDebugListeners.add(analyticsCollector);
videoListeners.add(analyticsCollector);
audioDebugListeners.add(analyticsCollector);
audioListeners.add(analyticsCollector);
addMetadataOutput(analyticsCollector);
bandwidthMeter.addEventListener(eventHandler, analyticsCollector);
- if (drmSessionManager instanceof DefaultDrmSessionManager) {
- ((DefaultDrmSessionManager) drmSessionManager).addListener(eventHandler, analyticsCollector);
- }
audioBecomingNoisyManager =
new AudioBecomingNoisyManager(context, eventHandler, componentListener);
audioFocusManager = new AudioFocusManager(context, eventHandler, componentListener);
+ streamVolumeManager = new StreamVolumeManager(context, eventHandler, componentListener);
wakeLockManager = new WakeLockManager(context);
+ wifiLockManager = new WifiLockManager(context);
+ deviceInfo = createDeviceInfo(streamVolumeManager);
}
@Override
@@ -490,6 +493,12 @@
return this;
}
+ @Override
+ @Nullable
+ public DeviceComponent getDeviceComponent() {
+ return this;
+ }
+
/**
* Sets the video scaling mode.
*
@@ -683,16 +692,18 @@
.send();
}
}
+ streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
for (AudioListener audioListener : audioListeners) {
audioListener.onAudioAttributesChanged(audioAttributes);
}
}
+ audioFocusManager.setAudioAttributes(handleAudioFocus ? audioAttributes : null);
+ boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
- int playerCommand =
- audioFocusManager.setAudioAttributes(
- handleAudioFocus ? audioAttributes : null, getPlayWhenReady(), getPlaybackState());
- updatePlayWhenReady(getPlayWhenReady(), playerCommand);
+ int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
+ updatePlayWhenReady(
+ playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
@Override
@@ -701,6 +712,27 @@
}
@Override
+ public void setAudioSessionId(int audioSessionId) {
+ verifyApplicationThread();
+ if (this.audioSessionId == audioSessionId) {
+ return;
+ }
+ this.audioSessionId = audioSessionId;
+ for (Renderer renderer : renderers) {
+ if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) {
+ player
+ .createMessage(renderer)
+ .setType(Renderer.MSG_SET_AUDIO_SESSION_ID)
+ .setPayload(audioSessionId)
+ .send();
+ }
+ }
+ if (audioSessionId != C.AUDIO_SESSION_ID_UNSET) {
+ notifyAudioSessionIdSet();
+ }
+ }
+
+ @Override
public int getAudioSessionId() {
return audioSessionId;
}
@@ -743,6 +775,30 @@
return audioVolume;
}
+ @Override
+ public boolean getSkipSilenceEnabled() {
+ return skipSilenceEnabled;
+ }
+
+ @Override
+ public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
+ verifyApplicationThread();
+ if (this.skipSilenceEnabled == skipSilenceEnabled) {
+ return;
+ }
+ this.skipSilenceEnabled = skipSilenceEnabled;
+ for (Renderer renderer : renderers) {
+ if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) {
+ player
+ .createMessage(renderer)
+ .setType(Renderer.MSG_SET_SKIP_SILENCE_ENABLED)
+ .setPayload(skipSilenceEnabled)
+ .send();
+ }
+ }
+ notifySkipSilenceEnabledChanged();
+ }
+
/**
* Sets the stream type for audio playback, used by the underlying audio track.
*
@@ -843,23 +899,19 @@
this.priorityTaskManager = priorityTaskManager;
}
- /**
- * Sets the {@link PlaybackParams} governing audio playback.
- *
- * @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}.
- * @param params The {@link PlaybackParams}, or null to clear any previously set parameters.
- */
+ /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
+ @SuppressWarnings("deprecation")
@Deprecated
- @TargetApi(23)
+ @RequiresApi(23)
public void setPlaybackParams(@Nullable PlaybackParams params) {
- PlaybackParameters playbackParameters;
+ float playbackSpeed;
if (params != null) {
params.allowDefaults();
- playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
+ playbackSpeed = params.getSpeed();
} else {
- playbackParameters = null;
+ playbackSpeed = 1.0f;
}
- setPlaybackParameters(playbackParameters);
+ setPlaybackSpeed(playbackSpeed);
}
/** Returns the video format currently being played, or null if no video is being played. */
@@ -960,6 +1012,11 @@
}
}
+ /** Returns whether skipping silences in the audio stream is enabled. */
+ public boolean isSkipSilenceEnabled() {
+ return skipSilenceEnabled;
+ }
+
/**
* Sets a listener to receive video events, removing all existing listeners.
*
@@ -1161,11 +1218,19 @@
return player.getPlaybackSuppressionReason();
}
+ /** @deprecated Use {@link #getPlayerError()} instead. */
+ @Deprecated
@Override
@Nullable
public ExoPlaybackException getPlaybackError() {
+ return getPlayerError();
+ }
+
+ @Override
+ @Nullable
+ public ExoPlaybackException getPlayerError() {
verifyApplicationThread();
- return player.getPlaybackError();
+ return player.getPlayerError();
}
/** @deprecated Use {@link #prepare()} instead. */
@@ -1179,9 +1244,11 @@
@Override
public void prepare() {
verifyApplicationThread();
+ boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
- int playerCommand = audioFocusManager.handlePrepare(getPlayWhenReady());
- updatePlayWhenReady(getPlayWhenReady(), playerCommand);
+ int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
+ updatePlayWhenReady(
+ playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
player.prepare();
}
@@ -1211,6 +1278,49 @@
}
@Override
+ public void setMediaItems(List<MediaItem> mediaItems) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItems(mediaItems);
+ }
+
+ @Override
+ public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItems(mediaItems, resetPosition);
+ }
+
+ @Override
+ public void setMediaItems(
+ List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItems(mediaItems, startWindowIndex, startPositionMs);
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItem(mediaItem);
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItem(mediaItem, resetPosition);
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
+ verifyApplicationThread();
+ analyticsCollector.resetForNewPlaylist();
+ player.setMediaItem(mediaItem, startPositionMs);
+ }
+
+ @Override
public void setMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread();
analyticsCollector.resetForNewPlaylist();
@@ -1254,6 +1364,30 @@
}
@Override
+ public void addMediaItems(List<MediaItem> mediaItems) {
+ verifyApplicationThread();
+ player.addMediaItems(mediaItems);
+ }
+
+ @Override
+ public void addMediaItems(int index, List<MediaItem> mediaItems) {
+ verifyApplicationThread();
+ player.addMediaItems(index, mediaItems);
+ }
+
+ @Override
+ public void addMediaItem(MediaItem mediaItem) {
+ verifyApplicationThread();
+ player.addMediaItem(mediaItem);
+ }
+
+ @Override
+ public void addMediaItem(int index, MediaItem mediaItem) {
+ verifyApplicationThread();
+ player.addMediaItem(index, mediaItem);
+ }
+
+ @Override
public void addMediaSource(MediaSource mediaSource) {
verifyApplicationThread();
player.addMediaSource(mediaSource);
@@ -1317,8 +1451,9 @@
public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread();
@AudioFocusManager.PlayerCommand
- int playerCommand = audioFocusManager.handleSetPlayWhenReady(playWhenReady, getPlaybackState());
- updatePlayWhenReady(playWhenReady, playerCommand);
+ int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
+ updatePlayWhenReady(
+ playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
@Override
@@ -1328,6 +1463,18 @@
}
@Override
+ public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
+ verifyApplicationThread();
+ player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems);
+ }
+
+ @Override
+ public boolean getPauseAtEndOfMediaItems() {
+ verifyApplicationThread();
+ return player.getPauseAtEndOfMediaItems();
+ }
+
+ @Override
public @RepeatMode int getRepeatMode() {
verifyApplicationThread();
return player.getRepeatMode();
@@ -1364,12 +1511,21 @@
player.seekTo(windowIndex, positionMs);
}
+ /**
+ * @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
+ * instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
verifyApplicationThread();
player.setPlaybackParameters(playbackParameters);
}
+ /** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
verifyApplicationThread();
@@ -1377,6 +1533,18 @@
}
@Override
+ public void setPlaybackSpeed(float playbackSpeed) {
+ verifyApplicationThread();
+ player.setPlaybackSpeed(playbackSpeed);
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ verifyApplicationThread();
+ return player.getPlaybackSpeed();
+ }
+
+ @Override
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
verifyApplicationThread();
player.setSeekParameters(seekParameters);
@@ -1397,8 +1565,8 @@
@Override
public void stop(boolean reset) {
verifyApplicationThread();
+ audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
player.stop(reset);
- audioFocusManager.handleStop();
currentCues = Collections.emptyList();
}
@@ -1406,8 +1574,10 @@
public void release() {
verifyApplicationThread();
audioBecomingNoisyManager.setEnabled(false);
- audioFocusManager.handleStop();
+ streamVolumeManager.release();
wakeLockManager.setStayAwake(false);
+ wifiLockManager.setStayAwake(false);
+ audioFocusManager.release();
player.release();
removeSurfaceCallbacks();
if (surface != null) {
@@ -1542,9 +1712,85 @@
*
* @param handleWakeLock Whether the player should use a {@link android.os.PowerManager.WakeLock}
* to ensure the device stays awake for playback, even when the screen is off.
+ * @deprecated Use {@link #setWakeMode(int)} instead.
*/
+ @Deprecated
public void setHandleWakeLock(boolean handleWakeLock) {
- wakeLockManager.setEnabled(handleWakeLock);
+ setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE);
+ }
+
+ /**
+ * Sets how the player should keep the device awake for playback when the screen is off.
+ *
+ * <p>Enabling this feature requires the {@link android.Manifest.permission#WAKE_LOCK} permission.
+ * It should be used together with a foreground {@link android.app.Service} for use cases where
+ * playback occurs and the screen is off (e.g. background audio playback). It is not useful when
+ * the screen will be kept on during playback (e.g. foreground video playback).
+ *
+ * <p>When enabled, the locks ({@link android.os.PowerManager.WakeLock} / {@link
+ * android.net.wifi.WifiManager.WifiLock}) will be held whenever the player is in the {@link
+ * #STATE_READY} or {@link #STATE_BUFFERING} states with {@code playWhenReady = true}. The locks
+ * held depends on the specified {@link C.WakeMode}.
+ *
+ * @param wakeMode The {@link C.WakeMode} option to keep the device awake during playback.
+ */
+ public void setWakeMode(@C.WakeMode int wakeMode) {
+ switch (wakeMode) {
+ case C.WAKE_MODE_NONE:
+ wakeLockManager.setEnabled(false);
+ wifiLockManager.setEnabled(false);
+ break;
+ case C.WAKE_MODE_LOCAL:
+ wakeLockManager.setEnabled(true);
+ wifiLockManager.setEnabled(false);
+ break;
+ case C.WAKE_MODE_NETWORK:
+ wakeLockManager.setEnabled(true);
+ wifiLockManager.setEnabled(true);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void addDeviceListener(DeviceListener listener) {
+ deviceListeners.add(listener);
+ }
+
+ @Override
+ public void removeDeviceListener(DeviceListener listener) {
+ deviceListeners.remove(listener);
+ }
+
+ @Override
+ public DeviceInfo getDeviceInfo() {
+ verifyApplicationThread();
+ return deviceInfo;
+ }
+
+ @Override
+ public int getDeviceVolume() {
+ verifyApplicationThread();
+ return streamVolumeManager.getVolume();
+ }
+
+ @Override
+ public void setDeviceVolume(int volume) {
+ verifyApplicationThread();
+ streamVolumeManager.setVolume(volume);
+ }
+
+ @Override
+ public void increaseDeviceVolume() {
+ verifyApplicationThread();
+ streamVolumeManager.increaseVolume();
+ }
+
+ @Override
+ public void decreaseDeviceVolume() {
+ verifyApplicationThread();
+ streamVolumeManager.decreaseVolume();
}
// Internal methods.
@@ -1633,15 +1879,62 @@
}
}
+ private void notifyAudioSessionIdSet() {
+ for (AudioListener audioListener : audioListeners) {
+ // Prevent duplicate notification if a listener is both a AudioRendererEventListener and
+ // a AudioListener, as they have the same method signature.
+ if (!audioDebugListeners.contains(audioListener)) {
+ audioListener.onAudioSessionId(audioSessionId);
+ }
+ }
+ for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
+ audioDebugListener.onAudioSessionId(audioSessionId);
+ }
+ }
+
+ @SuppressWarnings("SuspiciousMethodCalls")
+ private void notifySkipSilenceEnabledChanged() {
+ for (AudioListener listener : audioListeners) {
+ // Prevent duplicate notification if a listener is both a AudioRendererEventListener and
+ // a AudioListener, as they have the same method signature.
+ if (!audioDebugListeners.contains(listener)) {
+ listener.onSkipSilenceEnabledChanged(skipSilenceEnabled);
+ }
+ }
+ for (AudioRendererEventListener listener : audioDebugListeners) {
+ listener.onSkipSilenceEnabledChanged(skipSilenceEnabled);
+ }
+ }
+
private void updatePlayWhenReady(
- boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
+ boolean playWhenReady,
+ @AudioFocusManager.PlayerCommand int playerCommand,
+ @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE;
- player.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
+ player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
+ }
+
+ private void updateWakeAndWifiLock() {
+ @State int playbackState = getPlaybackState();
+ switch (playbackState) {
+ case Player.STATE_READY:
+ case Player.STATE_BUFFERING:
+ wakeLockManager.setStayAwake(getPlayWhenReady());
+ wifiLockManager.setStayAwake(getPlayWhenReady());
+ break;
+ case Player.STATE_ENDED:
+ case Player.STATE_IDLE:
+ wakeLockManager.setStayAwake(false);
+ wifiLockManager.setStayAwake(false);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
}
private void verifyApplicationThread() {
@@ -1655,6 +1948,19 @@
}
}
+ private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
+ return new DeviceInfo(
+ DeviceInfo.PLAYBACK_TYPE_LOCAL,
+ streamVolumeManager.getMinVolume(),
+ streamVolumeManager.getMaxVolume());
+ }
+
+ private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) {
+ return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
+ ? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
+ : PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
+ }
+
private final class ComponentListener
implements VideoRendererEventListener,
AudioRendererEventListener,
@@ -1664,6 +1970,7 @@
TextureView.SurfaceTextureListener,
AudioFocusManager.PlayerControl,
AudioBecomingNoisyManager.EventListener,
+ StreamVolumeManager.Listener,
Player.EventListener {
// VideoRendererEventListener implementation
@@ -1738,6 +2045,15 @@
videoDecoderCounters = null;
}
+ @Override
+ public void onVideoFrameProcessingOffset(
+ long totalProcessingOffsetUs, int frameCount, Format format) {
+ for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
+ videoDebugListener.onVideoFrameProcessingOffset(
+ totalProcessingOffsetUs, frameCount, format);
+ }
+ }
+
// AudioRendererEventListener implementation
@Override
@@ -1754,16 +2070,7 @@
return;
}
audioSessionId = sessionId;
- for (AudioListener audioListener : audioListeners) {
- // Prevent duplicate notification if a listener is both a AudioRendererEventListener and
- // a AudioListener, as they have the same method signature.
- if (!audioDebugListeners.contains(audioListener)) {
- audioListener.onAudioSessionId(sessionId);
- }
- }
- for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
- audioDebugListener.onAudioSessionId(sessionId);
- }
+ notifyAudioSessionIdSet();
}
@Override
@@ -1801,6 +2108,15 @@
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
}
+ @Override
+ public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
+ if (SimpleExoPlayer.this.skipSilenceEnabled == skipSilenceEnabled) {
+ return;
+ }
+ SimpleExoPlayer.this.skipSilenceEnabled = skipSilenceEnabled;
+ notifySkipSilenceEnabledChanged();
+ }
+
// TextOutput implementation
@Override
@@ -1872,20 +2188,45 @@
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
- updatePlayWhenReady(getPlayWhenReady(), playerCommand);
+ boolean playWhenReady = getPlayWhenReady();
+ updatePlayWhenReady(
+ playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
// AudioBecomingNoisyManager.EventListener implementation.
@Override
public void onAudioBecomingNoisy() {
- pause();
+ updatePlayWhenReady(
+ /* playWhenReady= */ false,
+ AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
+ Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
+ }
+
+ // StreamVolumeManager.Listener implementation.
+
+ @Override
+ public void onStreamTypeChanged(@C.StreamType int streamType) {
+ DeviceInfo deviceInfo = createDeviceInfo(streamVolumeManager);
+ if (!deviceInfo.equals(SimpleExoPlayer.this.deviceInfo)) {
+ SimpleExoPlayer.this.deviceInfo = deviceInfo;
+ for (DeviceListener deviceListener : deviceListeners) {
+ deviceListener.onDeviceInfoChanged(deviceInfo);
+ }
+ }
+ }
+
+ @Override
+ public void onStreamVolumeChanged(int streamVolume) {
+ for (DeviceListener deviceListener : deviceListeners) {
+ deviceListener.onDeviceVolumeChanged(streamVolume);
+ }
}
// Player.EventListener implementation.
@Override
- public void onLoadingChanged(boolean isLoading) {
+ public void onIsLoadingChanged(boolean isLoading) {
if (priorityTaskManager != null) {
if (isLoading && !isPriorityTaskManagerRegistered) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
@@ -1898,17 +2239,14 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {
- switch (playbackState) {
- case Player.STATE_READY:
- case Player.STATE_BUFFERING:
- wakeLockManager.setStayAwake(playWhenReady);
- break;
- case Player.STATE_ENDED:
- case Player.STATE_IDLE:
- wakeLockManager.setStayAwake(false);
- break;
- }
+ public void onPlaybackStateChanged(@State int playbackState) {
+ updateWakeAndWifiLock();
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
+ updateWakeAndWifiLock();
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java
new file mode 100644
index 0000000..28f439d
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Handler;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+
+/** A manager that wraps {@link AudioManager} to control/listen audio stream volume. */
+/* package */ final class StreamVolumeManager {
+
+ /** A listener for changes in the manager. */
+ public interface Listener {
+
+ /** Called when the audio stream type is changed. */
+ void onStreamTypeChanged(@C.StreamType int streamType);
+
+ /** Called when the audio stream volume is changed. */
+ void onStreamVolumeChanged(int streamVolume);
+ }
+
+ // TODO(b/151280453): Replace the hidden intent action with an official one.
+ // Copied from AudioManager#VOLUME_CHANGED_ACTION
+ private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
+
+ // TODO(b/153317944): Allow users to override these flags.
+ private static final int VOLUME_FLAGS = AudioManager.FLAG_SHOW_UI;
+
+ private final Context applicationContext;
+ private final Handler eventHandler;
+ private final Listener listener;
+ private final AudioManager audioManager;
+ private final VolumeChangeReceiver receiver;
+
+ @C.StreamType private int streamType;
+ private int volume;
+
+ /** Creates a manager. */
+ public StreamVolumeManager(Context context, Handler eventHandler, Listener listener) {
+ applicationContext = context.getApplicationContext();
+ this.eventHandler = eventHandler;
+ this.listener = listener;
+ audioManager =
+ Assertions.checkStateNotNull(
+ (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE));
+
+ streamType = C.STREAM_TYPE_DEFAULT;
+ volume = audioManager.getStreamVolume(streamType);
+
+ receiver = new VolumeChangeReceiver();
+ IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION);
+ applicationContext.registerReceiver(receiver, filter);
+ }
+
+ /** Sets the audio stream type. */
+ public void setStreamType(@C.StreamType int streamType) {
+ if (this.streamType == streamType) {
+ return;
+ }
+ this.streamType = streamType;
+
+ updateVolumeAndNotifyIfChanged();
+ listener.onStreamTypeChanged(streamType);
+ }
+
+ /**
+ * Gets the minimum volume for the current audio stream. It can be changed if {@link
+ * #setStreamType(int)} is called.
+ */
+ public int getMinVolume() {
+ return Util.SDK_INT >= 28 ? audioManager.getStreamMinVolume(streamType) : 0;
+ }
+
+ /**
+ * Gets the maximum volume for the current audio stream. It can be changed if {@link
+ * #setStreamType(int)} is called.
+ */
+ public int getMaxVolume() {
+ return audioManager.getStreamMaxVolume(streamType);
+ }
+
+ /** Gets the current volume for the current audio stream. */
+ public int getVolume() {
+ return volume;
+ }
+
+ /**
+ * Sets the volume with the given value for the current audio stream. The value should be between
+ * {@link #getMinVolume()} and {@link #getMaxVolume()}, otherwise it will be ignored.
+ */
+ public void setVolume(int volume) {
+ if (volume < getMinVolume() || volume > getMaxVolume()) {
+ return;
+ }
+ audioManager.setStreamVolume(streamType, volume, VOLUME_FLAGS);
+ updateVolumeAndNotifyIfChanged();
+ }
+
+ /**
+ * Increases the volume by one for the current audio stream. It will be ignored if the current
+ * volume is equal to {@link #getMaxVolume()}.
+ */
+ public void increaseVolume() {
+ if (volume >= getMaxVolume()) {
+ return;
+ }
+ audioManager.adjustStreamVolume(streamType, AudioManager.ADJUST_RAISE, VOLUME_FLAGS);
+ updateVolumeAndNotifyIfChanged();
+ }
+
+ /**
+ * Decreases the volume by one for the current audio stream. It will be ignored if the current
+ * volume is equal to {@link #getMinVolume()}.
+ */
+ public void decreaseVolume() {
+ if (volume <= getMinVolume()) {
+ return;
+ }
+ audioManager.adjustStreamVolume(streamType, AudioManager.ADJUST_LOWER, VOLUME_FLAGS);
+ updateVolumeAndNotifyIfChanged();
+ }
+
+ /** Releases the manager. It must be called when the manager is no longer required. */
+ public void release() {
+ applicationContext.unregisterReceiver(receiver);
+ }
+
+ private void updateVolumeAndNotifyIfChanged() {
+ int newVolume = audioManager.getStreamVolume(streamType);
+ if (volume != newVolume) {
+ volume = newVolume;
+ listener.onStreamVolumeChanged(newVolume);
+ }
+ }
+
+ private final class VolumeChangeReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ eventHandler.post(StreamVolumeManager.this::updateVolumeAndNotifyIfChanged);
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java
index b07a979..0d60044 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java
@@ -176,6 +176,12 @@
*/
public boolean isLive;
+ /**
+ * Whether this window contains placeholder information because the real information has yet to
+ * be loaded.
+ */
+ public boolean isPlaceholder;
+
/** The index of the first period that belongs to this window. */
public int firstPeriodIndex;
@@ -238,6 +244,7 @@
this.firstPeriodIndex = firstPeriodIndex;
this.lastPeriodIndex = lastPeriodIndex;
this.positionInFirstPeriodUs = positionInFirstPeriodUs;
+ this.isPlaceholder = false;
return this;
}
@@ -319,6 +326,7 @@
&& isSeekable == that.isSeekable
&& isDynamic == that.isDynamic
&& isLive == that.isLive
+ && isPlaceholder == that.isPlaceholder
&& defaultPositionUs == that.defaultPositionUs
&& durationUs == that.durationUs
&& firstPeriodIndex == that.firstPeriodIndex
@@ -340,6 +348,7 @@
result = 31 * result + (isSeekable ? 1 : 0);
result = 31 * result + (isDynamic ? 1 : 0);
result = 31 * result + (isLive ? 1 : 0);
+ result = 31 * result + (isPlaceholder ? 1 : 0);
result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32));
result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
result = 31 * result + firstPeriodIndex;
@@ -492,8 +501,8 @@
* microseconds.
*
* @param adGroupIndex The ad group index.
- * @return The time of the ad group at the index, in microseconds, or {@link
- * C#TIME_END_OF_SOURCE} for a post-roll ad group.
+ * @return The time of the ad group at the index relative to the start of the enclosing {@link
+ * Period}, in microseconds, or {@link C#TIME_END_OF_SOURCE} for a post-roll ad group.
*/
public long getAdGroupTimeUs(int adGroupIndex) {
return adPlaybackState.adGroupTimesUs[adGroupIndex];
@@ -536,22 +545,23 @@
}
/**
- * Returns the index of the ad group at or before {@code positionUs}, if that ad group is
- * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has
- * no ads remaining to be played, or if there is no such ad group.
+ * Returns the index of the ad group at or before {@code positionUs} in the period, if that ad
+ * group is unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code
+ * positionUs} has no ads remaining to be played, or if there is no such ad group.
*
- * @param positionUs The position at or before which to find an ad group, in microseconds.
+ * @param positionUs The period position at or before which to find an ad group, in
+ * microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexForPositionUs(long positionUs) {
- return adPlaybackState.getAdGroupIndexForPositionUs(positionUs);
+ return adPlaybackState.getAdGroupIndexForPositionUs(positionUs, durationUs);
}
/**
- * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be
- * played. Returns {@link C#INDEX_UNSET} if there is no such ad group.
+ * Returns the index of the next ad group after {@code positionUs} in the period that has ads
+ * remaining to be played. Returns {@link C#INDEX_UNSET} if there is no such ad group.
*
- * @param positionUs The position after which to find an ad group, in microseconds.
+ * @param positionUs The period position after which to find an ad group, in microseconds.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexAfterPositionUs(long positionUs) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java
index 1e71813..6de302d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java
@@ -39,7 +39,8 @@
private boolean stayAwake;
public WakeLockManager(Context context) {
- powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ powerManager =
+ (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE);
}
/**
@@ -48,18 +49,19 @@
* <p>By default, wake lock handling is not enabled. Enabling this will acquire the wake lock if
* necessary. Disabling this will release the wake lock if it is held.
*
- * @param enabled True if the player should handle a {@link WakeLock}, false otherwise. Please
- * note that enabling this requires the {@link android.Manifest.permission#WAKE_LOCK}
- * permission.
+ * <p>Enabling {@link WakeLock} requires the {@link android.Manifest.permission#WAKE_LOCK}.
+ *
+ * @param enabled True if the player should handle a {@link WakeLock}, false otherwise.
*/
public void setEnabled(boolean enabled) {
if (enabled) {
if (wakeLock == null) {
if (powerManager == null) {
- Log.w(TAG, "PowerManager was null, therefore the WakeLock was not created.");
+ Log.w(TAG, "PowerManager is null, therefore not creating the WakeLock.");
return;
}
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
+ wakeLock.setReferenceCounted(false);
}
}
@@ -86,15 +88,14 @@
// reasonable timeout that would not affect the user.
@SuppressLint("WakelockTimeout")
private void updateWakeLock() {
- // Needed for the library nullness check. If enabled is true, the wakelock will not be null.
- if (wakeLock != null) {
- if (enabled && stayAwake) {
- if (!wakeLock.isHeld()) {
- wakeLock.acquire();
- }
- } else if (wakeLock.isHeld()) {
- wakeLock.release();
- }
+ if (wakeLock == null) {
+ return;
+ }
+
+ if (enabled && stayAwake) {
+ wakeLock.acquire();
+ } else {
+ wakeLock.release();
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java
new file mode 100644
index 0000000..d3700a6
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.util.Log;
+
+/**
+ * Handles a {@link WifiLock}
+ *
+ * <p>The handling of wifi locks requires the {@link android.Manifest.permission#WAKE_LOCK}
+ * permission.
+ */
+/* package */ final class WifiLockManager {
+
+ private static final String TAG = "WifiLockManager";
+ private static final String WIFI_LOCK_TAG = "ExoPlayer:WifiLockManager";
+
+ @Nullable private final WifiManager wifiManager;
+ @Nullable private WifiLock wifiLock;
+ private boolean enabled;
+ private boolean stayAwake;
+
+ public WifiLockManager(Context context) {
+ wifiManager =
+ (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ }
+
+ /**
+ * Sets whether to enable the usage of a {@link WifiLock}.
+ *
+ * <p>By default, wifi lock handling is not enabled. Enabling will acquire the wifi lock if
+ * necessary. Disabling will release the wifi lock if held.
+ *
+ * <p>Enabling {@link WifiLock} requires the {@link android.Manifest.permission#WAKE_LOCK}.
+ *
+ * @param enabled True if the player should handle a {@link WifiLock}.
+ */
+ public void setEnabled(boolean enabled) {
+ if (enabled && wifiLock == null) {
+ if (wifiManager == null) {
+ Log.w(TAG, "WifiManager is null, therefore not creating the WifiLock.");
+ return;
+ }
+ wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, WIFI_LOCK_TAG);
+ wifiLock.setReferenceCounted(false);
+ }
+
+ this.enabled = enabled;
+ updateWifiLock();
+ }
+
+ /**
+ * Sets whether to acquire or release the {@link WifiLock}.
+ *
+ * <p>The wifi lock will not be acquired unless handling has been enabled through {@link
+ * #setEnabled(boolean)}.
+ *
+ * @param stayAwake True if the player should acquire the {@link WifiLock}. False if it should
+ * release.
+ */
+ public void setStayAwake(boolean stayAwake) {
+ this.stayAwake = stayAwake;
+ updateWifiLock();
+ }
+
+ private void updateWifiLock() {
+ if (wifiLock == null) {
+ return;
+ }
+
+ if (enabled && stayAwake) {
+ wifiLock.acquire();
+ } else {
+ wifiLock.release();
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
index 9fa9257..2af577f 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
@@ -31,7 +31,7 @@
import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.LoadEventInfo;
@@ -66,7 +66,7 @@
VideoRendererEventListener,
MediaSourceEventListener,
BandwidthMeter.EventListener,
- DefaultDrmSessionEventListener,
+ DrmSessionEventListener,
VideoListener,
AudioListener {
@@ -160,8 +160,7 @@
@Override
public final void onAudioEnabled(DecoderCounters counters) {
- // The renderers are only enabled after we changed the playing media period.
- EventTime eventTime = generatePlayingMediaPeriodEventTime();
+ EventTime eventTime = generateReadingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
}
@@ -221,6 +220,14 @@
}
@Override
+ public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
+ EventTime eventTime = generateReadingMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onSkipSilenceEnabledChanged(eventTime, skipSilenceEnabled);
+ }
+ }
+
+ @Override
public void onVolumeChanged(float audioVolume) {
EventTime eventTime = generateReadingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
@@ -232,8 +239,7 @@
@Override
public final void onVideoEnabled(DecoderCounters counters) {
- // The renderers are only enabled after we changed the playing media period.
- EventTime eventTime = generatePlayingMediaPeriodEventTime();
+ EventTime eventTime = generateReadingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
}
@@ -281,6 +287,15 @@
}
}
+ @Override
+ public final void onVideoFrameProcessingOffset(
+ long totalProcessingOffsetUs, int frameCount, Format format) {
+ EventTime eventTime = generatePlayingMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount, format);
+ }
+ }
+
// VideoListener implementation.
@Override
@@ -431,13 +446,14 @@
}
@Override
- public final void onLoadingChanged(boolean isLoading) {
+ public final void onIsLoadingChanged(boolean isLoading) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
- listener.onLoadingChanged(eventTime, isLoading);
+ listener.onIsLoadingChanged(eventTime, isLoading);
}
}
+ @SuppressWarnings("deprecation")
@Override
public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
@@ -447,6 +463,23 @@
}
@Override
+ public final void onPlaybackStateChanged(@Player.State int state) {
+ EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onPlaybackStateChanged(eventTime, state);
+ }
+ }
+
+ @Override
+ public final void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onPlayWhenReadyChanged(eventTime, playWhenReady, reason);
+ }
+ }
+
+ @Override
public void onPlaybackSuppressionReasonChanged(
@PlaybackSuppressionReason int playbackSuppressionReason) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
@@ -489,6 +522,9 @@
@Override
public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
+ if (reason == Player.DISCONTINUITY_REASON_SEEK) {
+ isSeeking = false;
+ }
mediaPeriodQueueTracker.onPositionDiscontinuity(Assertions.checkNotNull(player));
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
@@ -496,6 +532,12 @@
}
}
+ /**
+ * @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
+ * #onSkipSilenceEnabledChanged(boolean)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
@@ -505,13 +547,18 @@
}
@Override
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
+ EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onPlaybackSpeedChanged(eventTime, playbackSpeed);
+ }
+ }
+
+ @Override
public final void onSeekProcessed() {
- if (isSeeking) {
- isSeeking = false;
- EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
- for (AnalyticsListener listener : listeners) {
- listener.onSeekProcessed(eventTime);
- }
+ EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onSeekProcessed(eventTime);
}
}
@@ -676,7 +723,7 @@
@Nullable private MediaPeriodInfo currentPlayerMediaPeriod;
private @MonotonicNonNull MediaPeriodInfo playingMediaPeriod;
- @Nullable private MediaPeriodInfo readingMediaPeriod;
+ private @MonotonicNonNull MediaPeriodInfo readingMediaPeriod;
private Timeline timeline;
public MediaPeriodQueueTracker() {
@@ -711,7 +758,7 @@
/**
* Returns the {@link MediaPeriodInfo} of the media period currently being read by the player.
*
- * <p>May be null, if the player is not reading a media period.
+ * <p>May be null, if the player has not started reading any media period.
*/
@Nullable
public MediaPeriodInfo getReadingMediaPeriod() {
@@ -750,11 +797,15 @@
mediaPeriodInfoQueue.set(i, newMediaPeriodInfo);
mediaPeriodIdToInfo.put(newMediaPeriodInfo.mediaPeriodId, newMediaPeriodInfo);
}
- if (readingMediaPeriod != null) {
- readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline);
- }
if (!mediaPeriodInfoQueue.isEmpty()) {
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
+ } else if (playingMediaPeriod != null) {
+ playingMediaPeriod = updateMediaPeriodInfoToNewTimeline(playingMediaPeriod, timeline);
+ }
+ if (readingMediaPeriod != null) {
+ readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline);
+ } else if (playingMediaPeriod != null) {
+ readingMediaPeriod = playingMediaPeriod;
}
this.timeline = timeline;
currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player);
@@ -775,6 +826,9 @@
if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) {
currentPlayerMediaPeriod = playingMediaPeriod;
}
+ if (mediaPeriodInfoQueue.size() == 1) {
+ readingMediaPeriod = playingMediaPeriod;
+ }
}
/**
@@ -789,7 +843,10 @@
}
mediaPeriodInfoQueue.remove(mediaPeriodInfo);
if (readingMediaPeriod != null && mediaPeriodId.equals(readingMediaPeriod.mediaPeriodId)) {
- readingMediaPeriod = mediaPeriodInfoQueue.isEmpty() ? null : mediaPeriodInfoQueue.get(0);
+ readingMediaPeriod =
+ mediaPeriodInfoQueue.isEmpty()
+ ? Assertions.checkNotNull(playingMediaPeriod)
+ : mediaPeriodInfoQueue.get(0);
}
if (!mediaPeriodInfoQueue.isEmpty()) {
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
@@ -802,7 +859,12 @@
/** Update the queue with a change in the reading media period. */
public void onReadingStarted(MediaPeriodId mediaPeriodId) {
- readingMediaPeriod = mediaPeriodIdToInfo.get(mediaPeriodId);
+ @Nullable MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.get(mediaPeriodId);
+ if (mediaPeriodInfo == null) {
+ // The media period has already been removed from the queue in resetForNewPlaylist().
+ return;
+ }
+ readingMediaPeriod = mediaPeriodInfo;
}
@Nullable
@@ -817,7 +879,8 @@
? C.INDEX_UNSET
: playerTimeline
.getPeriod(playerPeriodIndex, period)
- .getAdGroupIndexAfterPositionUs(C.msToUs(player.getCurrentPosition()));
+ .getAdGroupIndexAfterPositionUs(
+ C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs());
for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) {
MediaPeriodInfo mediaPeriodInfo = mediaPeriodInfoQueue.get(i);
if (isMatchingMediaPeriod(
@@ -862,7 +925,8 @@
? C.INDEX_UNSET
: playerTimeline
.getPeriod(playerPeriodIndex, period)
- .getAdGroupIndexAfterPositionUs(C.msToUs(player.getCurrentPosition()));
+ .getAdGroupIndexAfterPositionUs(
+ C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs());
return isMatchingMediaPeriod(
playingMediaPeriod,
playerTimeline,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
index 353e7ac..77bc211 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
@@ -124,16 +124,32 @@
}
/**
- * Called when the player state changed.
- *
- * @param eventTime The event time.
- * @param playWhenReady Whether the playback will proceed when ready.
- * @param playbackState The new {@link Player.State playback state}.
+ * @deprecated Use {@link #onPlaybackStateChanged(EventTime, int)} and {@link
+ * #onPlayWhenReadyChanged(EventTime, boolean, int)} instead.
*/
+ @Deprecated
default void onPlayerStateChanged(
EventTime eventTime, boolean playWhenReady, @Player.State int playbackState) {}
/**
+ * Called when the playback state changed.
+ *
+ * @param eventTime The event time.
+ * @param state The new {@link Player.State playback state}.
+ */
+ default void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {}
+
+ /**
+ * Called when the value changed that indicates whether playback will proceed when ready.
+ *
+ * @param eventTime The event time.
+ * @param playWhenReady Whether playback will proceed when ready.
+ * @param reason The {@link Player.PlayWhenReadyChangeReason reason} of the change.
+ */
+ default void onPlayWhenReadyChanged(
+ EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {}
+
+ /**
* Called when playback suppression reason changed.
*
* @param eventTime The event time.
@@ -181,15 +197,23 @@
default void onSeekProcessed(EventTime eventTime) {}
/**
- * Called when the playback parameters changed.
- *
- * @param eventTime The event time.
- * @param playbackParameters The new playback parameters.
+ * @deprecated Use {@link #onPlaybackSpeedChanged(EventTime, float)} and {@link
+ * #onSkipSilenceEnabledChanged(EventTime, boolean)} instead.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
default void onPlaybackParametersChanged(
EventTime eventTime, PlaybackParameters playbackParameters) {}
/**
+ * Called when the playback speed changes.
+ *
+ * @param eventTime The event time.
+ * @param playbackSpeed The playback speed.
+ */
+ default void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {}
+
+ /**
* Called when the repeat mode changed.
*
* @param eventTime The event time.
@@ -211,6 +235,13 @@
* @param eventTime The event time.
* @param isLoading Whether the player is loading.
*/
+ @SuppressWarnings("deprecation")
+ default void onIsLoadingChanged(EventTime eventTime, boolean isLoading) {
+ onLoadingChanged(eventTime, isLoading);
+ }
+
+ /** @deprecated Use {@link #onIsLoadingChanged(EventTime, boolean)} instead. */
+ @Deprecated
default void onLoadingChanged(EventTime eventTime, boolean isLoading) {}
/**
@@ -428,6 +459,14 @@
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
/**
+ * Called when skipping silences is enabled or disabled in the audio stream.
+ *
+ * @param eventTime The event time.
+ * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled.
+ */
+ default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {}
+
+ /**
* Called after video frames have been dropped.
*
* @param eventTime The event time.
@@ -439,6 +478,30 @@
default void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}
/**
+ * Called when there is an update to the video frame processing offset reported by a video
+ * renderer.
+ *
+ * <p>Video processing offset represents how early a video frame is processed compared to the
+ * player's current position. For each video frame, the offset is calculated as <em>P<sub>vf</sub>
+ * - P<sub>pl</sub></em> where <em>P<sub>vf</sub></em> is the presentation timestamp of the video
+ * frame and <em>P<sub>pl</sub></em> is the current position of the player. Positive values
+ * indicate the frame was processed early enough whereas negative values indicate that the
+ * player's position had progressed beyond the frame's timestamp when the frame was processed (and
+ * the frame was probably dropped).
+ *
+ * <p>The renderer reports the sum of video processing offset samples (one sample per processed
+ * video frame: dropped, skipped or rendered) and the total number of samples (frames).
+ *
+ * @param eventTime The event time.
+ * @param totalProcessingOffsetUs The sum of video frame processing offset samples for all video
+ * frames processed by the renderer in microseconds.
+ * @param frameCount The number to samples included in the {@code totalProcessingOffsetUs}.
+ * @param format The current output {@link Format} rendered by the video renderer.
+ */
+ default void onVideoFrameProcessingOffset(
+ EventTime eventTime, long totalProcessingOffsetUs, int frameCount, Format format) {}
+
+ /**
* Called before a frame is rendered for the first time since setting the surface, and each time
* there's a change in the size or pixel aspect ratio of the video being rendered.
*
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
index 3f3803f..97805da 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
@@ -20,7 +20,6 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
@@ -51,7 +50,7 @@
* <p>For accurate measurements, the listener should be added to the player before loading media,
* i.e., {@link Player#getPlaybackState()} should be {@link Player#STATE_IDLE}.
*
- * <p>Playback stats are gathered separately for all playback session, i.e. each window in the
+ * <p>Playback stats are gathered separately for each playback session, i.e. each window in the
* {@link Timeline} and each single ad.
*/
public final class PlaybackStatsListener
@@ -133,6 +132,7 @@
*/
@Nullable
public PlaybackStats getPlaybackStats() {
+ @Nullable
PlaybackStatsTracker activeStatsTracker =
activeAdPlayback != null
? playbackStatsTrackers.get(activeAdPlayback)
@@ -173,8 +173,8 @@
if (isSeeking) {
tracker.onSeekStarted(eventTime, /* belongsToPlayback= */ true);
}
- tracker.onPlayerStateChanged(
- eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true);
+ tracker.onPlaybackStateChanged(eventTime, playbackState, /* belongsToPlayback= */ true);
+ tracker.onPlayWhenReadyChanged(eventTime, playWhenReady, /* belongsToPlayback= */ true);
tracker.onIsSuppressedChanged(eventTime, isSuppressed, /* belongsToPlayback= */ true);
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
playbackStatsTrackers.put(session, tracker);
@@ -194,11 +194,15 @@
@Override
public void onAdPlaybackStarted(EventTime eventTime, String contentSession, String adSession) {
Assertions.checkState(Assertions.checkNotNull(eventTime.mediaPeriodId).isAd());
- long contentPositionUs =
+ long contentPeriodPositionUs =
eventTime
.timeline
.getPeriodByUid(eventTime.mediaPeriodId.periodUid, period)
.getAdGroupTimeUs(eventTime.mediaPeriodId.adGroupIndex);
+ long contentWindowPositionUs =
+ contentPeriodPositionUs == C.TIME_END_OF_SOURCE
+ ? C.TIME_END_OF_SOURCE
+ : contentPeriodPositionUs + period.getPositionInWindowUs();
EventTime contentEventTime =
new EventTime(
eventTime.realtimeMs,
@@ -208,7 +212,7 @@
eventTime.mediaPeriodId.periodUid,
eventTime.mediaPeriodId.windowSequenceNumber,
eventTime.mediaPeriodId.adGroupIndex),
- /* eventPlaybackPositionMs= */ C.usToMs(contentPositionUs),
+ /* eventPlaybackPositionMs= */ C.usToMs(contentWindowPositionUs),
eventTime.currentPlaybackPositionMs,
eventTime.totalBufferedDurationMs);
Assertions.checkNotNull(playbackStatsTrackers.get(contentSession))
@@ -226,8 +230,7 @@
EventTime startEventTime = Assertions.checkNotNull(sessionStartEventTimes.remove(session));
if (automaticTransition) {
// Simulate ENDED state to record natural ending of playback.
- tracker.onPlayerStateChanged(
- eventTime, /* playWhenReady= */ true, Player.STATE_ENDED, /* belongsToPlayback= */ false);
+ tracker.onPlaybackStateChanged(eventTime, Player.STATE_ENDED, /* belongsToPlayback= */ false);
}
tracker.onFinished(eventTime);
PlaybackStats playbackStats = tracker.build(/* isFinal= */ true);
@@ -240,16 +243,27 @@
// AnalyticsListener implementation.
@Override
- public void onPlayerStateChanged(
- EventTime eventTime, boolean playWhenReady, @Player.State int playbackState) {
- this.playWhenReady = playWhenReady;
- this.playbackState = playbackState;
+ public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {
+ playbackState = state;
sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) {
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers
.get(session)
- .onPlayerStateChanged(eventTime, playWhenReady, playbackState, belongsToPlayback);
+ .onPlaybackStateChanged(eventTime, playbackState, belongsToPlayback);
+ }
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ this.playWhenReady = playWhenReady;
+ sessionManager.updateSessions(eventTime);
+ for (String session : playbackStatsTrackers.keySet()) {
+ boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
+ playbackStatsTrackers
+ .get(session)
+ .onPlayWhenReadyChanged(eventTime, playWhenReady, belongsToPlayback);
}
}
@@ -319,9 +333,8 @@
}
@Override
- public void onPlaybackParametersChanged(
- EventTime eventTime, PlaybackParameters playbackParameters) {
- playbackSpeed = playbackParameters.speed;
+ public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
+ this.playbackSpeed = playbackSpeed;
sessionManager.updateSessions(eventTime);
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
@@ -518,27 +531,36 @@
}
/**
- * Notifies the tracker of a player state change event, including all player state changes while
- * the playback is not in the foreground.
+ * Notifies the tracker of a playback state change event, including all playback state changes
+ * while the playback is not in the foreground.
+ *
+ * @param eventTime The {@link EventTime}.
+ * @param state The current {@link Player.State}.
+ * @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
+ */
+ public void onPlaybackStateChanged(
+ EventTime eventTime, @Player.State int state, boolean belongsToPlayback) {
+ playerPlaybackState = state;
+ if (state != Player.STATE_IDLE) {
+ hasFatalError = false;
+ }
+ if (state == Player.STATE_IDLE || state == Player.STATE_ENDED) {
+ isInterruptedByAd = false;
+ }
+ maybeUpdatePlaybackState(eventTime, belongsToPlayback);
+ }
+
+ /**
+ * Notifies the tracker of a play when ready change event, including all play when ready changes
+ * while the playback is not in the foreground.
*
* @param eventTime The {@link EventTime}.
* @param playWhenReady Whether the playback will proceed when ready.
- * @param playbackState The current {@link Player.State}.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/
- public void onPlayerStateChanged(
- EventTime eventTime,
- boolean playWhenReady,
- @Player.State int playbackState,
- boolean belongsToPlayback) {
+ public void onPlayWhenReadyChanged(
+ EventTime eventTime, boolean playWhenReady, boolean belongsToPlayback) {
this.playWhenReady = playWhenReady;
- playerPlaybackState = playbackState;
- if (playbackState != Player.STATE_IDLE) {
- hasFatalError = false;
- }
- if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
- isInterruptedByAd = false;
- }
maybeUpdatePlaybackState(eventTime, belongsToPlayback);
}
@@ -699,7 +721,8 @@
*/
public void onVideoSizeChanged(EventTime eventTime, int width, int height) {
if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE) {
- Format formatWithHeight = currentVideoFormat.copyWithVideoSize(width, height);
+ Format formatWithHeight =
+ currentVideoFormat.buildUpon().setWidth(width).setHeight(height).build();
maybeUpdateVideoFormat(eventTime, formatWithHeight);
}
}
@@ -940,6 +963,9 @@
}
private void maybeUpdateMediaTimeHistory(long realtimeMs, long mediaTimeMs) {
+ if (!keepHistory) {
+ return;
+ }
if (currentPlaybackState != PlaybackStats.PLAYBACK_STATE_PLAYING) {
if (mediaTimeMs == C.TIME_UNSET) {
return;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java
index 516df81..53eed6c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java
@@ -15,8 +15,8 @@
*/
package com.google.android.exoplayer2.audio;
-import android.annotation.TargetApi;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
@@ -118,7 +118,7 @@
*
* <p>Field {@link AudioAttributes#allowedCapturePolicy} is ignored for API levels prior to 29.
*/
- @TargetApi(21)
+ @RequiresApi(21)
public android.media.AudioAttributes getAudioAttributesV21() {
if (audioAttributesV21 == null) {
android.media.AudioAttributes.Builder builder =
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java
index 25c0e70..4ec8e51 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -25,11 +24,11 @@
import android.net.Uri;
import android.provider.Settings.Global;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/** Represents the set of audio formats that a device is capable of playing. */
-@TargetApi(21)
public final class AudioCapabilities {
private static final int DEFAULT_MAX_CHANNEL_COUNT = 8;
@@ -117,10 +116,10 @@
/**
* Returns whether this device supports playback of the specified audio {@code encoding}.
*
- * @param encoding One of {@link android.media.AudioFormat}'s {@code ENCODING_*} constants.
+ * @param encoding One of {@link C.Encoding}'s {@code ENCODING_*} constants.
* @return Whether this device supports playback the specified audio {@code encoding}.
*/
- public boolean supportsEncoding(int encoding) {
+ public boolean supportsEncoding(@C.Encoding int encoding) {
return Arrays.binarySearch(supportedEncodings, encoding) >= 0;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java
deleted file mode 100644
index ac4f632..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.audio;
-
-/** Thrown when an audio decoder error occurs. */
-public class AudioDecoderException extends Exception {
-
- /** @param message The detail message for this exception. */
- public AudioDecoderException(String message) {
- super(message);
- }
-
- /**
- * @param message The detail message for this exception.
- * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
- * A <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or unknown.
- */
- public AudioDecoderException(String message, Throwable cause) {
- super(message, cause);
- }
-
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java
index 8ce365b..f208f60 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioListener.java
@@ -38,4 +38,11 @@
* @param volume The new volume, with 0 being silence and 1 being unity gain.
*/
default void onVolumeChanged(float volume) {}
+
+ /**
+ * Called when skipping silences is enabled or disabled in the audio stream.
+ *
+ * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled.
+ */
+ default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
index bf5822c..7cb05cf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
@@ -85,8 +85,13 @@
default void onAudioDisabled(DecoderCounters counters) {}
/**
- * Dispatches events to a {@link AudioRendererEventListener}.
+ * Called when skipping silences is enabled or disabled in the audio stream.
+ *
+ * @param skipSilenceEnabled Whether skipping silences in the audio stream is enabled.
*/
+ default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
+
+ /** Dispatches events to a {@link AudioRendererEventListener}. */
final class EventDispatcher {
@Nullable private final Handler handler;
@@ -170,5 +175,12 @@
handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId));
}
}
+
+ /** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */
+ public void skipSilenceEnabledChanged(final boolean skipSilenceEnabled) {
+ if (handler != null) {
+ handler.post(() -> castNonNull(listener).onSkipSilenceEnabledChanged(skipSilenceEnabled));
+ }
+ }
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
index f2458a7..725e0a8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
@@ -28,18 +28,19 @@
* <p>Before starting playback, specify the input audio format by calling {@link #configure(int,
* int, int, int, int[], int, int)}.
*
- * <p>Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()}
- * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data.
+ * <p>Call {@link #handleBuffer(ByteBuffer, long, int)} to write data, and {@link
+ * #handleDiscontinuity()} when the data being fed is discontinuous. Call {@link #play()} to start
+ * playing the written data.
*
* <p>Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format
* changes. The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer,
- * long)}.
+ * long, int)}.
*
* <p>Call {@link #flush()} to prepare the sink to receive audio data from a new playback position.
*
* <p>Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers
- * will be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #flush()}.
- * Call {@link #reset()} when the instance is no longer required.
+ * will be provided via {@link #handleBuffer(ByteBuffer, long, int)} until the next {@link
+ * #flush()}. Call {@link #reset()} when the instance is no longer required.
*
* <p>The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link
* #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link
@@ -83,6 +84,12 @@
*/
void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
+ /**
+ * Called when skipping silences is enabled or disabled.
+ *
+ * @param skipSilenceEnabled Whether skipping silences is enabled.
+ */
+ void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled);
}
/**
@@ -234,11 +241,14 @@
*
* @param buffer The buffer containing audio data.
* @param presentationTimeUs The presentation timestamp of the buffer in microseconds.
+ * @param encodedAccessUnitCount The number of encoded access units in the buffer, or 1 if the
+ * buffer contains PCM audio. This allows batching multiple encoded access units in one
+ * buffer.
* @return Whether the buffer was handled fully.
* @throws InitializationException If an error occurs initializing the sink.
* @throws WriteException If an error occurs writing the audio data.
*/
- boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
+ boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException;
/**
@@ -259,18 +269,29 @@
boolean hasPendingData();
/**
- * Attempts to set the playback parameters. The audio sink may override these parameters if they
- * are not supported.
- *
- * @param playbackParameters The new playback parameters to attempt to set.
+ * @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
+ * instead.
*/
+ @Deprecated
void setPlaybackParameters(PlaybackParameters playbackParameters);
- /**
- * Gets the active {@link PlaybackParameters}.
- */
+ /** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
PlaybackParameters getPlaybackParameters();
+ /** Sets the playback speed. */
+ void setPlaybackSpeed(float playbackSpeed);
+
+ /** Gets the playback speed. */
+ float getPlaybackSpeed();
+
+ /** Sets whether silences should be skipped in the audio stream. */
+ void setSkipSilenceEnabled(boolean skipSilenceEnabled);
+
+ /** Gets whether silences are skipped in the audio stream. */
+ boolean getSkipSilenceEnabled();
+
/**
* Sets attributes for audio playback. If the attributes have changed and if the sink is not
* configured for use with tunneling, then it is reset and the audio session id is cleared.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java
index 0564591..9e87073 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java
@@ -20,6 +20,7 @@
import android.media.AudioTrack;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
@@ -115,6 +116,7 @@
* @param systemTimeUs The current system time, in microseconds.
* @return Whether the timestamp was updated.
*/
+ @TargetApi(19) // audioTimestamp will be null if Util.SDK_INT < 19.
public boolean maybePollTimestamp(long systemTimeUs) {
if (audioTimestamp == null || (systemTimeUs - lastTimestampSampleTimeUs) < sampleIntervalUs) {
return false;
@@ -220,6 +222,7 @@
* If {@link #maybePollTimestamp(long)} or {@link #hasTimestamp()} returned {@code true}, returns
* the system time at which the latest timestamp was sampled, in microseconds.
*/
+ @TargetApi(19) // audioTimestamp will be null if Util.SDK_INT < 19.
public long getTimestampSystemTimeUs() {
return audioTimestamp != null ? audioTimestamp.getTimestampSystemTimeUs() : C.TIME_UNSET;
}
@@ -228,6 +231,7 @@
* If {@link #maybePollTimestamp(long)} or {@link #hasTimestamp()} returned {@code true}, returns
* the latest timestamp's position in frames.
*/
+ @TargetApi(19) // audioTimestamp will be null if Util.SDK_INT < 19.
public long getTimestampPositionFrames() {
return audioTimestamp != null ? audioTimestamp.getTimestampPositionFrames() : C.POSITION_UNSET;
}
@@ -257,7 +261,7 @@
}
}
- @TargetApi(19)
+ @RequiresApi(19)
private static final class AudioTimestampV19 {
private final AudioTrack audioTrack;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
new file mode 100644
index 0000000..cc69d5c
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.audio;
+
+import android.media.audiofx.Virtualizer;
+import android.os.Handler;
+import android.os.SystemClock;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.BaseRenderer;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.PlayerMessage.Target;
+import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
+import com.google.android.exoplayer2.decoder.Decoder;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.decoder.DecoderException;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
+import com.google.android.exoplayer2.drm.DrmSession;
+import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
+import com.google.android.exoplayer2.source.SampleStream;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MediaClock;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.TraceUtil;
+import com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Decodes and renders audio using a {@link Decoder}.
+ *
+ * <p>This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)}
+ * on the playback thread:
+ *
+ * <ul>
+ * <li>Message with type {@link #MSG_SET_VOLUME} to set the volume. The message payload should be
+ * a {@link Float} with 0 being silence and 1 being unity gain.
+ * <li>Message with type {@link #MSG_SET_AUDIO_ATTRIBUTES} to set the audio attributes. The
+ * message payload should be an {@link com.google.android.exoplayer2.audio.AudioAttributes}
+ * instance that will configure the underlying audio track.
+ * <li>Message with type {@link #MSG_SET_AUX_EFFECT_INFO} to set the auxiliary effect. The message
+ * payload should be an {@link AuxEffectInfo} instance that will configure the underlying
+ * audio track.
+ * <li>Message with type {@link #MSG_SET_SKIP_SILENCE_ENABLED} to enable or disable skipping
+ * silences. The message payload should be a {@link Boolean}.
+ * <li>Message with type {@link #MSG_SET_AUDIO_SESSION_ID} to set the audio session ID. The
+ * message payload should be a session ID {@link Integer} that will be attached to the
+ * underlying audio track.
+ * </ul>
+ */
+public abstract class DecoderAudioRenderer extends BaseRenderer implements MediaClock {
+
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REINITIALIZATION_STATE_NONE,
+ REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
+ REINITIALIZATION_STATE_WAIT_END_OF_STREAM
+ })
+ private @interface ReinitializationState {}
+ /**
+ * The decoder does not need to be re-initialized.
+ */
+ private static final int REINITIALIZATION_STATE_NONE = 0;
+ /**
+ * The input format has changed in a way that requires the decoder to be re-initialized, but we
+ * haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
+ * ensure that it outputs any remaining buffers before we release it.
+ */
+ private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
+ /**
+ * The input format has changed in a way that requires the decoder to be re-initialized, and we've
+ * signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
+ * end of stream signal to indicate that it has output any remaining buffers before we release it.
+ */
+ private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
+
+ private final EventDispatcher eventDispatcher;
+ private final AudioSink audioSink;
+ private final DecoderInputBuffer flagsOnlyBuffer;
+
+ private DecoderCounters decoderCounters;
+ private Format inputFormat;
+ private int encoderDelay;
+ private int encoderPadding;
+
+ @Nullable
+ private Decoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
+ decoder;
+
+ @Nullable private DecoderInputBuffer inputBuffer;
+ @Nullable private SimpleOutputBuffer outputBuffer;
+ @Nullable private DrmSession decoderDrmSession;
+ @Nullable private DrmSession sourceDrmSession;
+
+ @ReinitializationState private int decoderReinitializationState;
+ private boolean decoderReceivedBuffers;
+ private boolean audioTrackNeedsConfigure;
+
+ private long currentPositionUs;
+ private boolean allowFirstBufferPositionDiscontinuity;
+ private boolean allowPositionDiscontinuity;
+ private boolean inputStreamEnded;
+ private boolean outputStreamEnded;
+ private boolean waitingForKeys;
+
+ public DecoderAudioRenderer() {
+ this(/* eventHandler= */ null, /* eventListener= */ null);
+ }
+
+ /**
+ * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
+ * null if delivery of events is not required.
+ * @param eventListener A listener of events. May be null if delivery of events is not required.
+ * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
+ */
+ public DecoderAudioRenderer(
+ @Nullable Handler eventHandler,
+ @Nullable AudioRendererEventListener eventListener,
+ AudioProcessor... audioProcessors) {
+ this(
+ eventHandler,
+ eventListener,
+ /* audioCapabilities= */ null,
+ audioProcessors);
+ }
+
+ /**
+ * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
+ * null if delivery of events is not required.
+ * @param eventListener A listener of events. May be null if delivery of events is not required.
+ * @param audioCapabilities The audio capabilities for playback on this device. May be null if the
+ * default capabilities (no encoded audio passthrough support) should be assumed.
+ * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
+ */
+ public DecoderAudioRenderer(
+ @Nullable Handler eventHandler,
+ @Nullable AudioRendererEventListener eventListener,
+ @Nullable AudioCapabilities audioCapabilities,
+ AudioProcessor... audioProcessors) {
+ this(eventHandler, eventListener, new DefaultAudioSink(audioCapabilities, audioProcessors));
+ }
+
+ /**
+ * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
+ * null if delivery of events is not required.
+ * @param eventListener A listener of events. May be null if delivery of events is not required.
+ * @param audioSink The sink to which audio will be output.
+ */
+ public DecoderAudioRenderer(
+ @Nullable Handler eventHandler,
+ @Nullable AudioRendererEventListener eventListener,
+ AudioSink audioSink) {
+ super(C.TRACK_TYPE_AUDIO);
+ eventDispatcher = new EventDispatcher(eventHandler, eventListener);
+ this.audioSink = audioSink;
+ audioSink.setListener(new AudioSinkListener());
+ flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
+ decoderReinitializationState = REINITIALIZATION_STATE_NONE;
+ audioTrackNeedsConfigure = true;
+ }
+
+ @Override
+ @Nullable
+ public MediaClock getMediaClock() {
+ return this;
+ }
+
+ @Override
+ @Capabilities
+ public final int supportsFormat(Format format) {
+ if (!MimeTypes.isAudio(format.sampleMimeType)) {
+ return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
+ }
+ @FormatSupport int formatSupport = supportsFormatInternal(format);
+ if (formatSupport <= FORMAT_UNSUPPORTED_DRM) {
+ return RendererCapabilities.create(formatSupport);
+ }
+ @TunnelingSupport
+ int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
+ return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
+ }
+
+ /**
+ * Returns the {@link FormatSupport} for the given {@link Format}.
+ *
+ * @param format The format, which has an audio {@link Format#sampleMimeType}.
+ * @return The {@link FormatSupport} for this {@link Format}.
+ */
+ @FormatSupport
+ protected abstract int supportsFormatInternal(Format format);
+
+ /**
+ * Returns whether the sink supports the audio format.
+ *
+ * @see AudioSink#supportsOutput(int, int)
+ */
+ protected final boolean supportsOutput(int channelCount, @C.Encoding int encoding) {
+ return audioSink.supportsOutput(channelCount, encoding);
+ }
+
+ @Override
+ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
+ if (outputStreamEnded) {
+ try {
+ audioSink.playToEndOfStream();
+ } catch (AudioSink.WriteException e) {
+ throw createRendererException(e, inputFormat);
+ }
+ return;
+ }
+
+ // Try and read a format if we don't have one already.
+ if (inputFormat == null) {
+ // We don't have a format yet, so try and read one.
+ FormatHolder formatHolder = getFormatHolder();
+ flagsOnlyBuffer.clear();
+ @SampleStream.ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, true);
+ if (result == C.RESULT_FORMAT_READ) {
+ onInputFormatChanged(formatHolder);
+ } else if (result == C.RESULT_BUFFER_READ) {
+ // End of stream read having not read a format.
+ Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
+ inputStreamEnded = true;
+ processEndOfStream();
+ return;
+ } else {
+ // We still don't have a format and can't make progress without one.
+ return;
+ }
+ }
+
+ // If we don't have a decoder yet, we need to instantiate one.
+ maybeInitDecoder();
+
+ if (decoder != null) {
+ try {
+ // Rendering loop.
+ TraceUtil.beginSection("drainAndFeed");
+ while (drainOutputBuffer()) {}
+ while (feedInputBuffer()) {}
+ TraceUtil.endSection();
+ } catch (DecoderException
+ | AudioSink.ConfigurationException
+ | AudioSink.InitializationException
+ | AudioSink.WriteException e) {
+ throw createRendererException(e, inputFormat);
+ }
+ decoderCounters.ensureUpdated();
+ }
+ }
+
+ /**
+ * Called when the audio session id becomes known. The default implementation is a no-op. One
+ * reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in
+ * order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
+ * should be released in {@link #onDisabled()} (if not before).
+ *
+ * <p>See {@link AudioSink.Listener#onAudioSessionId(int)}.
+ */
+ protected void onAudioSessionId(int audioSessionId) {
+ // Do nothing.
+ }
+
+ /** See {@link AudioSink.Listener#onPositionDiscontinuity()}. */
+ protected void onAudioTrackPositionDiscontinuity() {
+ // Do nothing.
+ }
+
+ /** See {@link AudioSink.Listener#onUnderrun(int, long, long)}. */
+ protected void onAudioTrackUnderrun(
+ int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
+ // Do nothing.
+ }
+
+ /** See {@link AudioSink.Listener#onSkipSilenceEnabledChanged(boolean)}. */
+ protected void onAudioTrackSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
+ // Do nothing.
+ }
+
+ /**
+ * Creates a decoder for the given format.
+ *
+ * @param format The format for which a decoder is required.
+ * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
+ * Maybe null and can be ignored if decoder does not handle encrypted content.
+ * @return The decoder.
+ * @throws DecoderException If an error occurred creating a suitable decoder.
+ */
+ protected abstract Decoder<
+ DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
+ createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) throws DecoderException;
+
+ /**
+ * Returns the format of audio buffers output by the decoder. Will not be called until the first
+ * output buffer has been dequeued, so the decoder may use input data to determine the format.
+ */
+ protected abstract Format getOutputFormat();
+
+ /**
+ * Returns whether the existing decoder can be kept for a new format.
+ *
+ * @param oldFormat The previous format.
+ * @param newFormat The new format.
+ * @return True if the existing decoder can be kept.
+ */
+ protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
+ return false;
+ }
+
+ private boolean drainOutputBuffer()
+ throws ExoPlaybackException, DecoderException, AudioSink.ConfigurationException,
+ AudioSink.InitializationException, AudioSink.WriteException {
+ if (outputBuffer == null) {
+ outputBuffer = decoder.dequeueOutputBuffer();
+ if (outputBuffer == null) {
+ return false;
+ }
+ if (outputBuffer.skippedOutputBufferCount > 0) {
+ decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
+ audioSink.handleDiscontinuity();
+ }
+ }
+
+ if (outputBuffer.isEndOfStream()) {
+ if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
+ // We're waiting to re-initialize the decoder, and have now processed all final buffers.
+ releaseDecoder();
+ maybeInitDecoder();
+ // The audio track may need to be recreated once the new output format is known.
+ audioTrackNeedsConfigure = true;
+ } else {
+ outputBuffer.release();
+ outputBuffer = null;
+ processEndOfStream();
+ }
+ return false;
+ }
+
+ if (audioTrackNeedsConfigure) {
+ Format outputFormat = getOutputFormat();
+ audioSink.configure(outputFormat.pcmEncoding, outputFormat.channelCount,
+ outputFormat.sampleRate, 0, null, encoderDelay, encoderPadding);
+ audioTrackNeedsConfigure = false;
+ }
+
+ if (audioSink.handleBuffer(
+ outputBuffer.data, outputBuffer.timeUs, /* encodedAccessUnitCount= */ 1)) {
+ decoderCounters.renderedOutputBufferCount++;
+ outputBuffer.release();
+ outputBuffer = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean feedInputBuffer() throws DecoderException, ExoPlaybackException {
+ if (decoder == null || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
+ || inputStreamEnded) {
+ // We need to reinitialize the decoder or the input stream has ended.
+ return false;
+ }
+
+ if (inputBuffer == null) {
+ inputBuffer = decoder.dequeueInputBuffer();
+ if (inputBuffer == null) {
+ return false;
+ }
+ }
+
+ if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
+ inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
+ decoder.queueInputBuffer(inputBuffer);
+ inputBuffer = null;
+ decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
+ return false;
+ }
+
+ @SampleStream.ReadDataResult int result;
+ FormatHolder formatHolder = getFormatHolder();
+ if (waitingForKeys) {
+ // We've already read an encrypted sample into buffer, and are waiting for keys.
+ result = C.RESULT_BUFFER_READ;
+ } else {
+ result = readSource(formatHolder, inputBuffer, false);
+ }
+
+ if (result == C.RESULT_NOTHING_READ) {
+ return false;
+ }
+ if (result == C.RESULT_FORMAT_READ) {
+ onInputFormatChanged(formatHolder);
+ return true;
+ }
+ if (inputBuffer.isEndOfStream()) {
+ inputStreamEnded = true;
+ decoder.queueInputBuffer(inputBuffer);
+ inputBuffer = null;
+ return false;
+ }
+ boolean bufferEncrypted = inputBuffer.isEncrypted();
+ waitingForKeys = shouldWaitForKeys(bufferEncrypted);
+ if (waitingForKeys) {
+ return false;
+ }
+ inputBuffer.flip();
+ onQueueInputBuffer(inputBuffer);
+ decoder.queueInputBuffer(inputBuffer);
+ decoderReceivedBuffers = true;
+ decoderCounters.inputBufferCount++;
+ inputBuffer = null;
+ return true;
+ }
+
+ private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
+ if (decoderDrmSession == null
+ || (!bufferEncrypted && decoderDrmSession.playClearSamplesWithoutKeys())) {
+ return false;
+ }
+ @DrmSession.State int drmSessionState = decoderDrmSession.getState();
+ if (drmSessionState == DrmSession.STATE_ERROR) {
+ throw createRendererException(decoderDrmSession.getError(), inputFormat);
+ }
+ return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
+ }
+
+ private void processEndOfStream() throws ExoPlaybackException {
+ outputStreamEnded = true;
+ try {
+ audioSink.playToEndOfStream();
+ } catch (AudioSink.WriteException e) {
+ // TODO(internal: b/145658993) Use outputFormat for the call from drainOutputBuffer.
+ throw createRendererException(e, inputFormat);
+ }
+ }
+
+ private void flushDecoder() throws ExoPlaybackException {
+ waitingForKeys = false;
+ if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
+ releaseDecoder();
+ maybeInitDecoder();
+ } else {
+ inputBuffer = null;
+ if (outputBuffer != null) {
+ outputBuffer.release();
+ outputBuffer = null;
+ }
+ decoder.flush();
+ decoderReceivedBuffers = false;
+ }
+ }
+
+ @Override
+ public boolean isEnded() {
+ return outputStreamEnded && audioSink.isEnded();
+ }
+
+ @Override
+ public boolean isReady() {
+ return audioSink.hasPendingData()
+ || (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null));
+ }
+
+ @Override
+ public long getPositionUs() {
+ if (getState() == STATE_STARTED) {
+ updateCurrentPosition();
+ }
+ return currentPositionUs;
+ }
+
+ @Override
+ public void setPlaybackSpeed(float playbackSpeed) {
+ audioSink.setPlaybackSpeed(playbackSpeed);
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ return audioSink.getPlaybackSpeed();
+ }
+
+ @Override
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ decoderCounters = new DecoderCounters();
+ eventDispatcher.enabled(decoderCounters);
+ int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
+ if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
+ audioSink.enableTunnelingV21(tunnelingAudioSessionId);
+ } else {
+ audioSink.disableTunneling();
+ }
+ }
+
+ @Override
+ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
+ audioSink.flush();
+ currentPositionUs = positionUs;
+ allowFirstBufferPositionDiscontinuity = true;
+ allowPositionDiscontinuity = true;
+ inputStreamEnded = false;
+ outputStreamEnded = false;
+ if (decoder != null) {
+ flushDecoder();
+ }
+ }
+
+ @Override
+ protected void onStarted() {
+ audioSink.play();
+ }
+
+ @Override
+ protected void onStopped() {
+ updateCurrentPosition();
+ audioSink.pause();
+ }
+
+ @Override
+ protected void onDisabled() {
+ inputFormat = null;
+ audioTrackNeedsConfigure = true;
+ waitingForKeys = false;
+ try {
+ setSourceDrmSession(null);
+ releaseDecoder();
+ audioSink.reset();
+ } finally {
+ eventDispatcher.disabled(decoderCounters);
+ }
+ }
+
+ @Override
+ public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
+ switch (messageType) {
+ case MSG_SET_VOLUME:
+ audioSink.setVolume((Float) message);
+ break;
+ case MSG_SET_AUDIO_ATTRIBUTES:
+ AudioAttributes audioAttributes = (AudioAttributes) message;
+ audioSink.setAudioAttributes(audioAttributes);
+ break;
+ case MSG_SET_AUX_EFFECT_INFO:
+ AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
+ audioSink.setAuxEffectInfo(auxEffectInfo);
+ break;
+ case MSG_SET_SKIP_SILENCE_ENABLED:
+ audioSink.setSkipSilenceEnabled((Boolean) message);
+ break;
+ case MSG_SET_AUDIO_SESSION_ID:
+ audioSink.setAudioSessionId((Integer) message);
+ break;
+ default:
+ super.handleMessage(messageType, message);
+ break;
+ }
+ }
+
+ private void maybeInitDecoder() throws ExoPlaybackException {
+ if (decoder != null) {
+ return;
+ }
+
+ setDecoderDrmSession(sourceDrmSession);
+
+ ExoMediaCrypto mediaCrypto = null;
+ if (decoderDrmSession != null) {
+ mediaCrypto = decoderDrmSession.getMediaCrypto();
+ if (mediaCrypto == null) {
+ DrmSessionException drmError = decoderDrmSession.getError();
+ if (drmError != null) {
+ // Continue for now. We may be able to avoid failure if the session recovers, or if a new
+ // input format causes the session to be replaced before it's used.
+ } else {
+ // The drm session isn't open yet.
+ return;
+ }
+ }
+ }
+
+ try {
+ long codecInitializingTimestamp = SystemClock.elapsedRealtime();
+ TraceUtil.beginSection("createAudioDecoder");
+ decoder = createDecoder(inputFormat, mediaCrypto);
+ TraceUtil.endSection();
+ long codecInitializedTimestamp = SystemClock.elapsedRealtime();
+ eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
+ codecInitializedTimestamp - codecInitializingTimestamp);
+ decoderCounters.decoderInitCount++;
+ } catch (DecoderException e) {
+ throw createRendererException(e, inputFormat);
+ }
+ }
+
+ private void releaseDecoder() {
+ inputBuffer = null;
+ outputBuffer = null;
+ decoderReinitializationState = REINITIALIZATION_STATE_NONE;
+ decoderReceivedBuffers = false;
+ if (decoder != null) {
+ decoder.release();
+ decoder = null;
+ decoderCounters.decoderReleaseCount++;
+ }
+ setDecoderDrmSession(null);
+ }
+
+ private void setSourceDrmSession(@Nullable DrmSession session) {
+ DrmSession.replaceSession(sourceDrmSession, session);
+ sourceDrmSession = session;
+ }
+
+ private void setDecoderDrmSession(@Nullable DrmSession session) {
+ DrmSession.replaceSession(decoderDrmSession, session);
+ decoderDrmSession = session;
+ }
+
+ private void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
+ Format newFormat = Assertions.checkNotNull(formatHolder.format);
+ setSourceDrmSession(formatHolder.drmSession);
+ Format oldFormat = inputFormat;
+ inputFormat = newFormat;
+
+ if (!canKeepCodec(oldFormat, inputFormat)) {
+ if (decoderReceivedBuffers) {
+ // Signal end of stream and wait for any final output buffers before re-initialization.
+ decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
+ } else {
+ // There aren't any final output buffers, so release the decoder immediately.
+ releaseDecoder();
+ maybeInitDecoder();
+ audioTrackNeedsConfigure = true;
+ }
+ }
+
+ encoderDelay = inputFormat.encoderDelay;
+ encoderPadding = inputFormat.encoderPadding;
+
+ eventDispatcher.inputFormatChanged(inputFormat);
+ }
+
+ private void onQueueInputBuffer(DecoderInputBuffer buffer) {
+ if (allowFirstBufferPositionDiscontinuity && !buffer.isDecodeOnly()) {
+ // TODO: Remove this hack once we have a proper fix for [Internal: b/71876314].
+ // Allow the position to jump if the first presentable input buffer has a timestamp that
+ // differs significantly from what was expected.
+ if (Math.abs(buffer.timeUs - currentPositionUs) > 500000) {
+ currentPositionUs = buffer.timeUs;
+ }
+ allowFirstBufferPositionDiscontinuity = false;
+ }
+ }
+
+ private void updateCurrentPosition() {
+ long newCurrentPositionUs = audioSink.getCurrentPositionUs(isEnded());
+ if (newCurrentPositionUs != AudioSink.CURRENT_POSITION_NOT_SET) {
+ currentPositionUs =
+ allowPositionDiscontinuity
+ ? newCurrentPositionUs
+ : Math.max(currentPositionUs, newCurrentPositionUs);
+ allowPositionDiscontinuity = false;
+ }
+ }
+
+ private final class AudioSinkListener implements AudioSink.Listener {
+
+ @Override
+ public void onAudioSessionId(int audioSessionId) {
+ eventDispatcher.audioSessionId(audioSessionId);
+ DecoderAudioRenderer.this.onAudioSessionId(audioSessionId);
+ }
+
+ @Override
+ public void onPositionDiscontinuity() {
+ onAudioTrackPositionDiscontinuity();
+ // We are out of sync so allow currentPositionUs to jump backwards.
+ DecoderAudioRenderer.this.allowPositionDiscontinuity = true;
+ }
+
+ @Override
+ public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
+ eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
+ onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
+ }
+
+ @Override
+ public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
+ eventDispatcher.skipSilenceEnabledChanged(skipSilenceEnabled);
+ onAudioTrackSkipSilenceEnabledChanged(skipSilenceEnabled);
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
index 0093f7c..a0aebdf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
@@ -16,14 +16,13 @@
package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.ConditionVariable;
import android.os.SystemClock;
-import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
@@ -31,9 +30,6 @@
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
@@ -85,16 +81,32 @@
AudioProcessor[] getAudioProcessors();
/**
- * Configures audio processors to apply the specified playback parameters immediately, returning
- * the new parameters, which may differ from those passed in. Only called when processors have
- * no input pending.
- *
- * @param playbackParameters The playback parameters to try to apply.
- * @return The playback parameters that were actually applied.
+ * @deprecated Use {@link #applyPlaybackSpeed(float)} and {@link
+ * #applySkipSilenceEnabled(boolean)} instead.
*/
+ @Deprecated
PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters);
/**
+ * Configures audio processors to apply the specified playback speed immediately, returning the
+ * new playback speed, which may differ from the speed passed in. Only called when processors
+ * have no input pending.
+ *
+ * @param playbackSpeed The playback speed to try to apply.
+ * @return The playback speed that was actually applied.
+ */
+ float applyPlaybackSpeed(float playbackSpeed);
+
+ /**
+ * Configures audio processors to apply whether to skip silences immediately, returning the new
+ * value. Only called when processors have no input pending.
+ *
+ * @param skipSilenceEnabled Whether silences should be skipped in the audio stream.
+ * @return The new value.
+ */
+ boolean applySkipSilenceEnabled(boolean skipSilenceEnabled);
+
+ /**
* Scales the specified playout duration to take into account speedup due to audio processing,
* returning an input media duration, in arbitrary units.
*/
@@ -142,13 +154,26 @@
return audioProcessors;
}
+ /**
+ * @deprecated Use {@link #applyPlaybackSpeed(float)} and {@link
+ * #applySkipSilenceEnabled(boolean)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) {
- silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence);
- return new PlaybackParameters(
- sonicAudioProcessor.setSpeed(playbackParameters.speed),
- sonicAudioProcessor.setPitch(playbackParameters.pitch),
- playbackParameters.skipSilence);
+ return new PlaybackParameters(applyPlaybackSpeed(playbackParameters.speed));
+ }
+
+ @Override
+ public float applyPlaybackSpeed(float playbackSpeed) {
+ return sonicAudioProcessor.setSpeed(playbackSpeed);
+ }
+
+ @Override
+ public boolean applySkipSilenceEnabled(boolean skipSilenceEnabled) {
+ silenceSkippingAudioProcessor.setEnabled(skipSilenceEnabled);
+ return skipSilenceEnabled;
}
@Override
@@ -204,19 +229,13 @@
*/
@SuppressLint("InlinedApi")
private static final int WRITE_NON_BLOCKING = AudioTrack.WRITE_NON_BLOCKING;
+ /** The default playback speed. */
+ private static final float DEFAULT_PLAYBACK_SPEED = 1.0f;
+ /** The default skip silence flag. */
+ private static final boolean DEFAULT_SKIP_SILENCE = false;
private static final String TAG = "AudioTrack";
- /** Represents states of the {@link #startMediaTimeUs} value. */
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({START_NOT_SET, START_IN_SYNC, START_NEED_SYNC})
- private @interface StartMediaTimeState {}
-
- private static final int START_NOT_SET = 0;
- private static final int START_IN_SYNC = 1;
- private static final int START_NEED_SYNC = 2;
-
/**
* Whether to enable a workaround for an issue where an audio effect does not keep its session
* active across releasing/initializing a new audio track, on platform builds where
@@ -237,14 +256,14 @@
@Nullable private final AudioCapabilities audioCapabilities;
private final AudioProcessorChain audioProcessorChain;
- private final boolean enableConvertHighResIntPcmToFloat;
+ private final boolean enableFloatOutput;
private final ChannelMappingAudioProcessor channelMappingAudioProcessor;
private final TrimmingAudioProcessor trimmingAudioProcessor;
private final AudioProcessor[] toIntPcmAvailableAudioProcessors;
private final AudioProcessor[] toFloatPcmAvailableAudioProcessors;
private final ConditionVariable releasingConditionVariable;
private final AudioTrackPositionTracker audioTrackPositionTracker;
- private final ArrayDeque<PlaybackParametersCheckpoint> playbackParametersCheckpoints;
+ private final ArrayDeque<MediaPositionParameters> mediaPositionParametersCheckpoints;
@Nullable private Listener listener;
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize(long)}). */
@@ -255,10 +274,8 @@
private AudioTrack audioTrack;
private AudioAttributes audioAttributes;
- @Nullable private PlaybackParameters afterDrainPlaybackParameters;
- private PlaybackParameters playbackParameters;
- private long playbackParametersOffsetUs;
- private long playbackParametersPositionUs;
+ @Nullable private MediaPositionParameters afterDrainParameters;
+ private MediaPositionParameters mediaPositionParameters;
@Nullable private ByteBuffer avSyncHeader;
private int bytesUntilNextAvSync;
@@ -268,13 +285,14 @@
private long writtenPcmBytes;
private long writtenEncodedFrames;
private int framesPerEncodedSample;
- private @StartMediaTimeState int startMediaTimeState;
+ private boolean startMediaTimeUsNeedsSync;
private long startMediaTimeUs;
private float volume;
private AudioProcessor[] activeAudioProcessors;
private ByteBuffer[] outputBuffers;
@Nullable private ByteBuffer inputBuffer;
+ private int inputBufferAccessUnitCount;
@Nullable private ByteBuffer outputBuffer;
private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset;
@@ -298,7 +316,7 @@
*/
public DefaultAudioSink(
@Nullable AudioCapabilities audioCapabilities, AudioProcessor[] audioProcessors) {
- this(audioCapabilities, audioProcessors, /* enableConvertHighResIntPcmToFloat= */ false);
+ this(audioCapabilities, audioProcessors, /* enableFloatOutput= */ false);
}
/**
@@ -308,19 +326,16 @@
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio before
* output. May be empty.
- * @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
- * integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
- * audio processing (for example, speed and pitch adjustment) will not be available when float
- * output is in use.
+ * @param enableFloatOutput Whether to enable 32-bit float output. Where possible, 32-bit float
+ * output will be used if the input is 32-bit float, and also if the input is high resolution
+ * (24-bit or 32-bit) integer PCM. Audio processing (for example, speed adjustment) will not
+ * be available when float output is in use.
*/
public DefaultAudioSink(
@Nullable AudioCapabilities audioCapabilities,
AudioProcessor[] audioProcessors,
- boolean enableConvertHighResIntPcmToFloat) {
- this(
- audioCapabilities,
- new DefaultAudioProcessorChain(audioProcessors),
- enableConvertHighResIntPcmToFloat);
+ boolean enableFloatOutput) {
+ this(audioCapabilities, new DefaultAudioProcessorChain(audioProcessors), enableFloatOutput);
}
/**
@@ -331,18 +346,18 @@
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param audioProcessorChain An {@link AudioProcessorChain} which is used to apply playback
* parameters adjustments. The instance passed in must not be reused in other sinks.
- * @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution
- * integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer
- * audio processing (for example, speed and pitch adjustment) will not be available when float
- * output is in use.
+ * @param enableFloatOutput Whether to enable 32-bit float output. Where possible, 32-bit float
+ * output will be used if the input is 32-bit float, and also if the input is high resolution
+ * (24-bit or 32-bit) integer PCM. Audio processing (for example, speed adjustment) will not
+ * be available when float output is in use.
*/
public DefaultAudioSink(
@Nullable AudioCapabilities audioCapabilities,
AudioProcessorChain audioProcessorChain,
- boolean enableConvertHighResIntPcmToFloat) {
+ boolean enableFloatOutput) {
this.audioCapabilities = audioCapabilities;
this.audioProcessorChain = Assertions.checkNotNull(audioProcessorChain);
- this.enableConvertHighResIntPcmToFloat = enableConvertHighResIntPcmToFloat;
+ this.enableFloatOutput = enableFloatOutput;
releasingConditionVariable = new ConditionVariable(true);
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
@@ -357,15 +372,19 @@
toIntPcmAvailableAudioProcessors = toIntPcmAudioProcessors.toArray(new AudioProcessor[0]);
toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()};
volume = 1.0f;
- startMediaTimeState = START_NOT_SET;
audioAttributes = AudioAttributes.DEFAULT;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
- playbackParameters = PlaybackParameters.DEFAULT;
+ mediaPositionParameters =
+ new MediaPositionParameters(
+ DEFAULT_PLAYBACK_SPEED,
+ DEFAULT_SKIP_SILENCE,
+ /* mediaTimeUs= */ 0,
+ /* audioTrackPositionUs= */ 0);
drainingAudioProcessorIndex = C.INDEX_UNSET;
activeAudioProcessors = new AudioProcessor[0];
outputBuffers = new ByteBuffer[0];
- playbackParametersCheckpoints = new ArrayDeque<>();
+ mediaPositionParametersCheckpoints = new ArrayDeque<>();
}
// AudioSink implementation.
@@ -393,12 +412,12 @@
@Override
public long getCurrentPositionUs(boolean sourceEnded) {
- if (!isInitialized() || startMediaTimeState == START_NOT_SET) {
+ if (!isInitialized()) {
return CURRENT_POSITION_NOT_SET;
}
long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded);
positionUs = Math.min(positionUs, configuration.framesToDurationUs(getWrittenFrames()));
- return startMediaTimeUs + applySkipping(applySpeedup(positionUs));
+ return applySkipping(applyMediaPositionParameters(positionUs));
}
@Override
@@ -421,37 +440,34 @@
}
boolean isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
- boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT;
+ boolean processingEnabled = isInputPcm;
int sampleRate = inputSampleRate;
int channelCount = inputChannelCount;
@C.Encoding int encoding = inputEncoding;
- boolean shouldConvertHighResIntPcmToFloat =
- enableConvertHighResIntPcmToFloat
+ boolean useFloatOutput =
+ enableFloatOutput
&& supportsOutput(inputChannelCount, C.ENCODING_PCM_FLOAT)
- && Util.isEncodingHighResolutionIntegerPcm(inputEncoding);
+ && Util.isEncodingHighResolutionPcm(inputEncoding);
AudioProcessor[] availableAudioProcessors =
- shouldConvertHighResIntPcmToFloat
- ? toFloatPcmAvailableAudioProcessors
- : toIntPcmAvailableAudioProcessors;
+ useFloatOutput ? toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors;
if (processingEnabled) {
trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames);
channelMappingAudioProcessor.setChannelMap(outputChannels);
- AudioProcessor.AudioFormat inputAudioFormat =
+ AudioProcessor.AudioFormat outputFormat =
new AudioProcessor.AudioFormat(sampleRate, channelCount, encoding);
- AudioProcessor.AudioFormat outputAudioFormat = inputAudioFormat;
for (AudioProcessor audioProcessor : availableAudioProcessors) {
try {
- outputAudioFormat = audioProcessor.configure(inputAudioFormat);
+ AudioProcessor.AudioFormat nextFormat = audioProcessor.configure(outputFormat);
+ if (audioProcessor.isActive()) {
+ outputFormat = nextFormat;
+ }
} catch (UnhandledAudioFormatException e) {
throw new ConfigurationException(e);
}
- if (audioProcessor.isActive()) {
- inputAudioFormat = outputAudioFormat;
- }
}
- sampleRate = outputAudioFormat.sampleRate;
- channelCount = outputAudioFormat.channelCount;
- encoding = outputAudioFormat.encoding;
+ sampleRate = outputFormat.sampleRate;
+ channelCount = outputFormat.channelCount;
+ encoding = outputFormat.encoding;
}
int outputChannelConfig = getChannelConfig(channelCount, isInputPcm);
@@ -463,7 +479,7 @@
isInputPcm ? Util.getPcmFrameSize(inputEncoding, inputChannelCount) : C.LENGTH_UNSET;
int outputPcmFrameSize =
isInputPcm ? Util.getPcmFrameSize(encoding, channelCount) : C.LENGTH_UNSET;
- boolean canApplyPlaybackParameters = processingEnabled && !shouldConvertHighResIntPcmToFloat;
+ boolean canApplyPlaybackParameters = processingEnabled && !useFloatOutput;
Configuration pendingConfiguration =
new Configuration(
isInputPcm,
@@ -540,7 +556,10 @@
}
}
- applyPlaybackParameters(playbackParameters, presentationTimeUs);
+ startMediaTimeUs = Math.max(0, presentationTimeUs);
+ startMediaTimeUsNeedsSync = false;
+
+ applyPlaybackSpeedAndSkipSilence(presentationTimeUs);
audioTrackPositionTracker.setAudioTrack(
audioTrack,
@@ -567,19 +586,18 @@
@Override
public void handleDiscontinuity() {
// Force resynchronization after a skipped buffer.
- if (startMediaTimeState == START_IN_SYNC) {
- startMediaTimeState = START_NEED_SYNC;
- }
+ startMediaTimeUsNeedsSync = true;
}
@Override
@SuppressWarnings("ReferenceEquality")
- public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
+ public boolean handleBuffer(
+ ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException {
Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer);
if (pendingConfiguration != null) {
- if (!drainAudioProcessorsToEndOfStream()) {
+ if (!drainToEndOfStream()) {
// There's still pending data in audio processors to write to the track.
return false;
} else if (!pendingConfiguration.canReuseAudioTrack(configuration)) {
@@ -595,7 +613,7 @@
pendingConfiguration = null;
}
// Re-apply playback parameters.
- applyPlaybackParameters(playbackParameters, presentationTimeUs);
+ applyPlaybackSpeedAndSkipSilence(presentationTimeUs);
}
if (!isInitialized()) {
@@ -628,60 +646,63 @@
}
}
- if (afterDrainPlaybackParameters != null) {
- if (!drainAudioProcessorsToEndOfStream()) {
+ if (afterDrainParameters != null) {
+ if (!drainToEndOfStream()) {
// Don't process any more input until draining completes.
return false;
}
- PlaybackParameters newPlaybackParameters = afterDrainPlaybackParameters;
- afterDrainPlaybackParameters = null;
- applyPlaybackParameters(newPlaybackParameters, presentationTimeUs);
+ applyPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ afterDrainParameters = null;
}
- if (startMediaTimeState == START_NOT_SET) {
- startMediaTimeUs = Math.max(0, presentationTimeUs);
- startMediaTimeState = START_IN_SYNC;
- } else {
- // Sanity check that presentationTimeUs is consistent with the expected value.
- long expectedPresentationTimeUs =
- startMediaTimeUs
- + configuration.inputFramesToDurationUs(
- getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
- if (startMediaTimeState == START_IN_SYNC
- && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
- Log.e(TAG, "Discontinuity detected [expected " + expectedPresentationTimeUs + ", got "
- + presentationTimeUs + "]");
- startMediaTimeState = START_NEED_SYNC;
+ // Sanity check that presentationTimeUs is consistent with the expected value.
+ long expectedPresentationTimeUs =
+ startMediaTimeUs
+ + configuration.inputFramesToDurationUs(
+ getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
+ if (!startMediaTimeUsNeedsSync
+ && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
+ Log.e(
+ TAG,
+ "Discontinuity detected [expected "
+ + expectedPresentationTimeUs
+ + ", got "
+ + presentationTimeUs
+ + "]");
+ startMediaTimeUsNeedsSync = true;
+ }
+ if (startMediaTimeUsNeedsSync) {
+ if (!drainToEndOfStream()) {
+ // Don't update timing until pending AudioProcessor buffers are completely drained.
+ return false;
}
- if (startMediaTimeState == START_NEED_SYNC) {
- // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the
- // number of bytes submitted.
- long adjustmentUs = presentationTimeUs - expectedPresentationTimeUs;
- startMediaTimeUs += adjustmentUs;
- startMediaTimeState = START_IN_SYNC;
- if (listener != null && adjustmentUs != 0) {
- listener.onPositionDiscontinuity();
- }
+ // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the
+ // number of bytes submitted.
+ long adjustmentUs = presentationTimeUs - expectedPresentationTimeUs;
+ startMediaTimeUs += adjustmentUs;
+ startMediaTimeUsNeedsSync = false;
+ // Re-apply playback parameters because the startMediaTimeUs changed.
+ applyPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ if (listener != null && adjustmentUs != 0) {
+ listener.onPositionDiscontinuity();
}
}
if (configuration.isInputPcm) {
submittedPcmBytes += buffer.remaining();
} else {
- submittedEncodedFrames += framesPerEncodedSample;
+ submittedEncodedFrames += framesPerEncodedSample * encodedAccessUnitCount;
}
inputBuffer = buffer;
+ inputBufferAccessUnitCount = encodedAccessUnitCount;
}
- if (configuration.processingEnabled) {
- processBuffers(presentationTimeUs);
- } else {
- writeBuffer(inputBuffer, presentationTimeUs);
- }
+ processBuffers(presentationTimeUs);
if (!inputBuffer.hasRemaining()) {
inputBuffer = null;
+ inputBufferAccessUnitCount = 0;
return true;
}
@@ -776,7 +797,10 @@
}
if (bytesWritten == bytesRemaining) {
if (!configuration.isInputPcm) {
- writtenEncodedFrames += framesPerEncodedSample;
+ // When playing non-PCM, the inputBuffer is never processed, thus the last inputBuffer
+ // must be the current input buffer.
+ Assertions.checkState(buffer == inputBuffer);
+ writtenEncodedFrames += framesPerEncodedSample * inputBufferAccessUnitCount;
}
outputBuffer = null;
}
@@ -784,13 +808,13 @@
@Override
public void playToEndOfStream() throws WriteException {
- if (!handledEndOfStream && isInitialized() && drainAudioProcessorsToEndOfStream()) {
+ if (!handledEndOfStream && isInitialized() && drainToEndOfStream()) {
playPendingData();
handledEndOfStream = true;
}
}
- private boolean drainAudioProcessorsToEndOfStream() throws WriteException {
+ private boolean drainToEndOfStream() throws WriteException {
boolean audioProcessorNeedsEndOfStream = false;
if (drainingAudioProcessorIndex == C.INDEX_UNSET) {
drainingAudioProcessorIndex =
@@ -831,34 +855,44 @@
return isInitialized() && audioTrackPositionTracker.hasPendingData(getWrittenFrames());
}
+ /**
+ * @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
+ * instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
- if (configuration != null && !configuration.canApplyPlaybackParameters) {
- this.playbackParameters = PlaybackParameters.DEFAULT;
- return;
- }
- PlaybackParameters lastSetPlaybackParameters = getPlaybackParameters();
- if (!playbackParameters.equals(lastSetPlaybackParameters)) {
- if (isInitialized()) {
- // Drain the audio processors so we can determine the frame position at which the new
- // parameters apply.
- afterDrainPlaybackParameters = playbackParameters;
- } else {
- // Update the playback parameters now. They will be applied to the audio processors during
- // initialization.
- this.playbackParameters = playbackParameters;
- }
- }
+ setPlaybackSpeedAndSkipSilence(playbackParameters.speed, getSkipSilenceEnabled());
+ }
+
+ /** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public PlaybackParameters getPlaybackParameters() {
+ MediaPositionParameters mediaPositionParameters = getMediaPositionParameters();
+ return new PlaybackParameters(mediaPositionParameters.playbackSpeed);
}
@Override
- public PlaybackParameters getPlaybackParameters() {
- // Mask the already set parameters.
- return afterDrainPlaybackParameters != null
- ? afterDrainPlaybackParameters
- : !playbackParametersCheckpoints.isEmpty()
- ? playbackParametersCheckpoints.getLast().playbackParameters
- : playbackParameters;
+ public void setPlaybackSpeed(float playbackSpeed) {
+ setPlaybackSpeedAndSkipSilence(playbackSpeed, getSkipSilenceEnabled());
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ return getMediaPositionParameters().playbackSpeed;
+ }
+
+ @Override
+ public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
+ setPlaybackSpeedAndSkipSilence(getPlaybackSpeed(), skipSilenceEnabled);
+ }
+
+ @Override
+ public boolean getSkipSilenceEnabled() {
+ return getMediaPositionParameters().skipSilence;
}
@Override
@@ -954,25 +988,25 @@
writtenPcmBytes = 0;
writtenEncodedFrames = 0;
framesPerEncodedSample = 0;
- if (afterDrainPlaybackParameters != null) {
- playbackParameters = afterDrainPlaybackParameters;
- afterDrainPlaybackParameters = null;
- } else if (!playbackParametersCheckpoints.isEmpty()) {
- playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters;
- }
- playbackParametersCheckpoints.clear();
- playbackParametersOffsetUs = 0;
- playbackParametersPositionUs = 0;
+ mediaPositionParameters =
+ new MediaPositionParameters(
+ getPlaybackSpeed(),
+ getSkipSilenceEnabled(),
+ /* mediaTimeUs= */ 0,
+ /* audioTrackPositionUs= */ 0);
+ startMediaTimeUs = 0;
+ afterDrainParameters = null;
+ mediaPositionParametersCheckpoints.clear();
trimmingAudioProcessor.resetTrimmedFrameCount();
flushAudioProcessors();
inputBuffer = null;
+ inputBufferAccessUnitCount = 0;
outputBuffer = null;
stoppedAudioTrack = false;
handledEndOfStream = false;
drainingAudioProcessorIndex = C.INDEX_UNSET;
avSyncHeader = null;
bytesUntilNextAvSync = 0;
- startMediaTimeState = START_NOT_SET;
if (audioTrackPositionTracker.isPlaying()) {
audioTrack.pause();
}
@@ -985,7 +1019,7 @@
}
audioTrackPositionTracker.reset();
releasingConditionVariable.close();
- new Thread() {
+ new Thread("ExoPlayer:AudioTrackReleaseThread") {
@Override
public void run() {
try {
@@ -1013,9 +1047,9 @@
playing = false;
}
- /**
- * Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}.
- */
+ // Internal methods.
+
+ /** Releases {@link #keepSessionIdAudioTrack} asynchronously, if it is non-{@code null}. */
private void releaseKeepSessionIdAudioTrack() {
if (keepSessionIdAudioTrack == null) {
return;
@@ -1032,47 +1066,85 @@
}.start();
}
- private void applyPlaybackParameters(
- PlaybackParameters playbackParameters, long presentationTimeUs) {
- PlaybackParameters newPlaybackParameters =
- configuration.canApplyPlaybackParameters
- ? audioProcessorChain.applyPlaybackParameters(playbackParameters)
- : PlaybackParameters.DEFAULT;
- // Store the position and corresponding media time from which the parameters will apply.
- playbackParametersCheckpoints.add(
- new PlaybackParametersCheckpoint(
- newPlaybackParameters,
- /* mediaTimeUs= */ Math.max(0, presentationTimeUs),
- /* positionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
- setupAudioProcessors();
+ private void setPlaybackSpeedAndSkipSilence(float playbackSpeed, boolean skipSilence) {
+ MediaPositionParameters currentMediaPositionParameters = getMediaPositionParameters();
+ if (playbackSpeed != currentMediaPositionParameters.playbackSpeed
+ || skipSilence != currentMediaPositionParameters.skipSilence) {
+ MediaPositionParameters mediaPositionParameters =
+ new MediaPositionParameters(
+ playbackSpeed,
+ skipSilence,
+ /* mediaTimeUs= */ C.TIME_UNSET,
+ /* audioTrackPositionUs= */ C.TIME_UNSET);
+ if (isInitialized()) {
+ // Drain the audio processors so we can determine the frame position at which the new
+ // parameters apply.
+ this.afterDrainParameters = mediaPositionParameters;
+ } else {
+ // Update the audio processor chain parameters now. They will be applied to the audio
+ // processors during initialization.
+ this.mediaPositionParameters = mediaPositionParameters;
+ }
+ }
}
- private long applySpeedup(long positionUs) {
- @Nullable PlaybackParametersCheckpoint checkpoint = null;
- while (!playbackParametersCheckpoints.isEmpty()
- && positionUs >= playbackParametersCheckpoints.getFirst().positionUs) {
- checkpoint = playbackParametersCheckpoints.remove();
+ private MediaPositionParameters getMediaPositionParameters() {
+ // Mask the already set parameters.
+ return afterDrainParameters != null
+ ? afterDrainParameters
+ : !mediaPositionParametersCheckpoints.isEmpty()
+ ? mediaPositionParametersCheckpoints.getLast()
+ : mediaPositionParameters;
+ }
+
+ private void applyPlaybackSpeedAndSkipSilence(long presentationTimeUs) {
+ float playbackSpeed =
+ configuration.canApplyPlaybackParameters
+ ? audioProcessorChain.applyPlaybackSpeed(getPlaybackSpeed())
+ : DEFAULT_PLAYBACK_SPEED;
+ boolean skipSilenceEnabled =
+ configuration.canApplyPlaybackParameters
+ ? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
+ : DEFAULT_SKIP_SILENCE;
+ mediaPositionParametersCheckpoints.add(
+ new MediaPositionParameters(
+ playbackSpeed,
+ skipSilenceEnabled,
+ /* mediaTimeUs= */ Math.max(0, presentationTimeUs),
+ /* audioTrackPositionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
+ setupAudioProcessors();
+ if (listener != null) {
+ listener.onSkipSilenceEnabledChanged(skipSilenceEnabled);
}
- if (checkpoint != null) {
- // We are playing (or about to play) media with the new playback parameters, so update them.
- playbackParameters = checkpoint.playbackParameters;
- playbackParametersPositionUs = checkpoint.positionUs;
- playbackParametersOffsetUs = checkpoint.mediaTimeUs - startMediaTimeUs;
+ }
+
+ /**
+ * Applies and updates media position parameters.
+ *
+ * @param positionUs The current audio track position, in microseconds.
+ * @return The current media time, in microseconds.
+ */
+ private long applyMediaPositionParameters(long positionUs) {
+ while (!mediaPositionParametersCheckpoints.isEmpty()
+ && positionUs >= mediaPositionParametersCheckpoints.getFirst().audioTrackPositionUs) {
+ // We are playing (or about to play) media with the new parameters, so update them.
+ mediaPositionParameters = mediaPositionParametersCheckpoints.remove();
}
- if (playbackParameters.speed == 1f) {
- return positionUs + playbackParametersOffsetUs - playbackParametersPositionUs;
+ long playoutDurationSinceLastCheckpoint =
+ positionUs - mediaPositionParameters.audioTrackPositionUs;
+ if (mediaPositionParameters.playbackSpeed != 1f) {
+ if (mediaPositionParametersCheckpoints.isEmpty()) {
+ playoutDurationSinceLastCheckpoint =
+ audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpoint);
+ } else {
+ // Playing data at a previous playback speed, so fall back to multiplying by the speed.
+ playoutDurationSinceLastCheckpoint =
+ Util.getMediaDurationForPlayoutDuration(
+ playoutDurationSinceLastCheckpoint, mediaPositionParameters.playbackSpeed);
+ }
}
-
- if (playbackParametersCheckpoints.isEmpty()) {
- return playbackParametersOffsetUs
- + audioProcessorChain.getMediaDuration(positionUs - playbackParametersPositionUs);
- }
-
- // We are playing data at a previous playback speed, so fall back to multiplying by the speed.
- return playbackParametersOffsetUs
- + Util.getMediaDurationForPlayoutDuration(
- positionUs - playbackParametersPositionUs, playbackParameters.speed);
+ return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpoint;
}
private long applySkipping(long positionUs) {
@@ -1129,26 +1201,38 @@
private static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) {
switch (encoding) {
+ case C.ENCODING_MP3:
+ return MpegAudioUtil.MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_LC:
+ return AacUtil.AAC_LC_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_HE_V1:
+ return AacUtil.AAC_HE_V1_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_HE_V2:
+ return AacUtil.AAC_HE_V2_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_XHE:
+ return AacUtil.AAC_XHE_MAX_RATE_BYTES_PER_SECOND;
+ case C.ENCODING_AAC_ELD:
+ return AacUtil.AAC_ELD_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_AC3:
- return 640 * 1000 / 8;
+ return Ac3Util.AC3_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_E_AC3:
case C.ENCODING_E_AC3_JOC:
- return 6144 * 1000 / 8;
+ return Ac3Util.E_AC3_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_AC4:
- return 2688 * 1000 / 8;
+ return Ac4Util.MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_DTS:
- // DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
- return 1536 * 1000 / 8;
+ return DtsUtil.DTS_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_DTS_HD:
- return 18000 * 1000 / 8;
+ return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_DOLBY_TRUEHD:
- return 24500 * 1000 / 8;
- case C.ENCODING_INVALID:
+ return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
case C.ENCODING_PCM_16BIT:
+ case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_8BIT:
case C.ENCODING_PCM_FLOAT:
+ case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
throw new IllegalArgumentException();
@@ -1159,6 +1243,15 @@
switch (encoding) {
case C.ENCODING_MP3:
return MpegAudioUtil.parseMpegAudioFrameSampleCount(buffer.get(buffer.position()));
+ case C.ENCODING_AAC_LC:
+ return AacUtil.AAC_LC_AUDIO_SAMPLE_COUNT;
+ case C.ENCODING_AAC_HE_V1:
+ case C.ENCODING_AAC_HE_V2:
+ return AacUtil.AAC_HE_AUDIO_SAMPLE_COUNT;
+ case C.ENCODING_AAC_XHE:
+ return AacUtil.AAC_XHE_AUDIO_SAMPLE_COUNT;
+ case C.ENCODING_AAC_ELD:
+ return AacUtil.AAC_LD_AUDIO_SAMPLE_COUNT;
case C.ENCODING_DTS:
case C.ENCODING_DTS_HD:
return DtsUtil.parseDtsAudioSampleCount(buffer);
@@ -1174,19 +1267,27 @@
? 0
: (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset)
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT);
+ case C.ENCODING_PCM_16BIT:
+ case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
+ case C.ENCODING_PCM_24BIT:
+ case C.ENCODING_PCM_32BIT:
+ case C.ENCODING_PCM_8BIT:
+ case C.ENCODING_PCM_FLOAT:
+ case C.ENCODING_INVALID:
+ case Format.NO_VALUE:
default:
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
}
}
- @TargetApi(21)
+ @RequiresApi(21)
private static int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, WRITE_NON_BLOCKING);
}
- @TargetApi(21)
- private int writeNonBlockingWithAvSyncV21(AudioTrack audioTrack, ByteBuffer buffer, int size,
- long presentationTimeUs) {
+ @RequiresApi(21)
+ private int writeNonBlockingWithAvSyncV21(
+ AudioTrack audioTrack, ByteBuffer buffer, int size, long presentationTimeUs) {
if (Util.SDK_INT >= 26) {
// The underlying platform AudioTrack writes AV sync headers directly.
return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000);
@@ -1222,7 +1323,7 @@
return result;
}
- @TargetApi(21)
+ @RequiresApi(21)
private static void setVolumeInternalV21(AudioTrack audioTrack, float volume) {
audioTrack.setVolume(volume);
}
@@ -1240,20 +1341,25 @@
}
}
- /** Stores playback parameters with the position and media time at which they apply. */
- private static final class PlaybackParametersCheckpoint {
+ /** Stores parameters used to calculate the current media position. */
+ private static final class MediaPositionParameters {
- private final PlaybackParameters playbackParameters;
- private final long mediaTimeUs;
- private final long positionUs;
+ /** The playback speed. */
+ public final float playbackSpeed;
+ /** Whether to skip silences. */
+ public final boolean skipSilence;
+ /** The media time from which the playback parameters apply, in microseconds. */
+ public final long mediaTimeUs;
+ /** The audio track position from which the playback parameters apply, in microseconds. */
+ public final long audioTrackPositionUs;
- private PlaybackParametersCheckpoint(PlaybackParameters playbackParameters, long mediaTimeUs,
- long positionUs) {
- this.playbackParameters = playbackParameters;
+ private MediaPositionParameters(
+ float playbackSpeed, boolean skipSilence, long mediaTimeUs, long audioTrackPositionUs) {
+ this.playbackSpeed = playbackSpeed;
+ this.skipSilence = skipSilence;
this.mediaTimeUs = mediaTimeUs;
- this.positionUs = positionUs;
+ this.audioTrackPositionUs = audioTrackPositionUs;
}
-
}
private final class PositionTrackerListener implements AudioTrackPositionTracker.Listener {
@@ -1424,7 +1530,7 @@
return audioTrack;
}
- @TargetApi(21)
+ @RequiresApi(21)
private AudioTrack createAudioTrackV21(
boolean tunneling, AudioAttributes audioAttributes, int audioSessionId) {
android.media.AudioAttributes attributes;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java
index a75e675..ca6b4f3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java
@@ -16,13 +16,19 @@
package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
/**
- * An {@link AudioProcessor} that converts 24-bit and 32-bit integer PCM audio to 32-bit float PCM
- * audio.
+ * An {@link AudioProcessor} that converts high resolution PCM audio to 32-bit float. The following
+ * encodings are supported as input:
+ *
+ * <ul>
+ * <li>{@link C#ENCODING_PCM_24BIT}
+ * <li>{@link C#ENCODING_PCM_32BIT}
+ * <li>{@link C#ENCODING_PCM_FLOAT} ({@link #isActive()} will return {@code false})
+ * </ul>
*/
/* package */ final class FloatResamplingAudioProcessor extends BaseAudioProcessor {
@@ -32,10 +38,11 @@
@Override
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException {
- if (!Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)) {
+ @C.PcmEncoding int encoding = inputAudioFormat.encoding;
+ if (!Util.isEncodingHighResolutionPcm(encoding)) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
- return Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)
+ return encoding != C.ENCODING_PCM_FLOAT
? new AudioFormat(
inputAudioFormat.sampleRate, inputAudioFormat.channelCount, C.ENCODING_PCM_FLOAT)
: AudioFormat.NOT_SET;
@@ -43,31 +50,42 @@
@Override
public void queueInput(ByteBuffer inputBuffer) {
- Assertions.checkState(Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding));
- boolean isInput32Bit = inputAudioFormat.encoding == C.ENCODING_PCM_32BIT;
int position = inputBuffer.position();
int limit = inputBuffer.limit();
int size = limit - position;
- int resampledSize = isInput32Bit ? size : (size / 3) * 4;
- ByteBuffer buffer = replaceOutputBuffer(resampledSize);
- if (isInput32Bit) {
- for (int i = position; i < limit; i += 4) {
- int pcm32BitInteger =
- (inputBuffer.get(i) & 0xFF)
- | ((inputBuffer.get(i + 1) & 0xFF) << 8)
- | ((inputBuffer.get(i + 2) & 0xFF) << 16)
- | ((inputBuffer.get(i + 3) & 0xFF) << 24);
- writePcm32BitFloat(pcm32BitInteger, buffer);
- }
- } else { // Input is 24-bit PCM.
- for (int i = position; i < limit; i += 3) {
- int pcm32BitInteger =
- ((inputBuffer.get(i) & 0xFF) << 8)
- | ((inputBuffer.get(i + 1) & 0xFF) << 16)
- | ((inputBuffer.get(i + 2) & 0xFF) << 24);
- writePcm32BitFloat(pcm32BitInteger, buffer);
- }
+ ByteBuffer buffer;
+ switch (inputAudioFormat.encoding) {
+ case C.ENCODING_PCM_24BIT:
+ buffer = replaceOutputBuffer((size / 3) * 4);
+ for (int i = position; i < limit; i += 3) {
+ int pcm32BitInteger =
+ ((inputBuffer.get(i) & 0xFF) << 8)
+ | ((inputBuffer.get(i + 1) & 0xFF) << 16)
+ | ((inputBuffer.get(i + 2) & 0xFF) << 24);
+ writePcm32BitFloat(pcm32BitInteger, buffer);
+ }
+ break;
+ case C.ENCODING_PCM_32BIT:
+ buffer = replaceOutputBuffer(size);
+ for (int i = position; i < limit; i += 4) {
+ int pcm32BitInteger =
+ (inputBuffer.get(i) & 0xFF)
+ | ((inputBuffer.get(i + 1) & 0xFF) << 8)
+ | ((inputBuffer.get(i + 2) & 0xFF) << 16)
+ | ((inputBuffer.get(i + 3) & 0xFF) << 24);
+ writePcm32BitFloat(pcm32BitInteger, buffer);
+ }
+ break;
+ case C.ENCODING_PCM_8BIT:
+ case C.ENCODING_PCM_16BIT:
+ case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
+ case C.ENCODING_PCM_FLOAT:
+ case C.ENCODING_INVALID:
+ case Format.NO_VALUE:
+ default:
+ // Never happens.
+ throw new IllegalStateException();
}
inputBuffer.position(inputBuffer.limit());
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
index 704bd11..7ab1cda 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
@@ -74,9 +74,10 @@
}
@Override
- public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
+ public boolean handleBuffer(
+ ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException {
- return sink.handleBuffer(buffer, presentationTimeUs);
+ return sink.handleBuffer(buffer, presentationTimeUs, encodedAccessUnitCount);
}
@Override
@@ -94,17 +95,46 @@
return sink.hasPendingData();
}
+ /**
+ * @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
+ * instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
sink.setPlaybackParameters(playbackParameters);
}
+ /** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
return sink.getPlaybackParameters();
}
@Override
+ public void setPlaybackSpeed(float playbackSpeed) {
+ sink.setPlaybackSpeed(playbackSpeed);
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ return sink.getPlaybackSpeed();
+ }
+
+ @Override
+ public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
+ sink.setSkipSilenceEnabled(skipSilenceEnabled);
+ }
+
+ @Override
+ public boolean getSkipSilenceEnabled() {
+ return sink.getSkipSilenceEnabled();
+ }
+
+ @Override
public void setAudioAttributes(AudioAttributes audioAttributes) {
sink.setAudioAttributes(audioAttributes);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
index dcc066e..90fb1f8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
@@ -22,28 +22,22 @@
import android.media.MediaFormat;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
-import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
@@ -67,17 +61,15 @@
* <li>Message with type {@link #MSG_SET_AUX_EFFECT_INFO} to set the auxiliary effect. The message
* payload should be an {@link AuxEffectInfo} instance that will configure the underlying
* audio track.
+ * <li>Message with type {@link #MSG_SET_SKIP_SILENCE_ENABLED} to enable or disable skipping
+ * silences. The message payload should be a {@link Boolean}.
+ * <li>Message with type {@link #MSG_SET_AUDIO_SESSION_ID} to set the audio session ID. The
+ * message payload should be a session ID {@link Integer} that will be attached to the
+ * underlying audio track.
* </ul>
*/
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock {
- /**
- * Maximum number of tracked pending stream change times. Generally there is zero or one pending
- * stream change. We track more to allow for pending changes that have fewer samples than the
- * codec latency.
- */
- private static final int MAX_PENDING_STREAM_CHANGE_COUNT = 10;
-
private static final String TAG = "MediaCodecAudioRenderer";
/**
* Custom key used to indicate bits per sample by some decoders on Vivo devices. For example
@@ -88,7 +80,6 @@
private final Context context;
private final EventDispatcher eventDispatcher;
private final AudioSink audioSink;
- private final long[] pendingStreamChangeTimesUs;
private int codecMaxInputSize;
private boolean passthroughEnabled;
@@ -99,48 +90,15 @@
private long currentPositionUs;
private boolean allowFirstBufferPositionDiscontinuity;
private boolean allowPositionDiscontinuity;
- private long lastInputTimeUs;
- private int pendingStreamChangeCount;
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
*/
- @SuppressWarnings("deprecation")
public MediaCodecAudioRenderer(Context context, MediaCodecSelector mediaCodecSelector) {
this(
context,
mediaCodecSelector,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false);
- }
-
- /**
- * @param context A context.
- * @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @deprecated Use {@link #MediaCodecAudioRenderer(Context, MediaCodecSelector, boolean, Handler,
- * AudioRendererEventListener, AudioSink)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public MediaCodecAudioRenderer(
- Context context,
- MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys) {
- this(
- context,
- mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
/* eventHandler= */ null,
/* eventListener= */ null);
}
@@ -152,7 +110,6 @@
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
- @SuppressWarnings("deprecation")
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@@ -161,43 +118,6 @@
this(
context,
mediaCodecSelector,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- eventHandler,
- eventListener);
- }
-
- /**
- * @param context A context.
- * @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @deprecated Use {@link #MediaCodecAudioRenderer(Context, MediaCodecSelector, boolean, Handler,
- * AudioRendererEventListener, AudioSink)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public MediaCodecAudioRenderer(
- Context context,
- MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener) {
- this(
- context,
- mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
eventHandler,
eventListener,
(AudioCapabilities) null);
@@ -206,13 +126,6 @@
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
@@ -220,17 +133,10 @@
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param audioProcessors Optional {@link AudioProcessor}s that will process PCM audio before
* output.
- * @deprecated Use {@link #MediaCodecAudioRenderer(Context, MediaCodecSelector, boolean, Handler,
- * AudioRendererEventListener, AudioSink)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
*/
- @Deprecated
- @SuppressWarnings("deprecation")
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities,
@@ -238,8 +144,6 @@
this(
context,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
eventHandler,
eventListener,
new DefaultAudioSink(audioCapabilities, audioProcessors));
@@ -248,36 +152,20 @@
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioSink The sink to which audio will be output.
- * @deprecated Use {@link #MediaCodecAudioRenderer(Context, MediaCodecSelector, boolean, Handler,
- * AudioRendererEventListener, AudioSink)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
*/
- @Deprecated
- @SuppressWarnings("deprecation")
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) {
this(
context,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
/* enableDecoderFallback= */ false,
eventHandler,
eventListener,
@@ -295,7 +183,6 @@
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioSink The sink to which audio will be output.
*/
- @SuppressWarnings("deprecation")
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@@ -303,69 +190,25 @@
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) {
- this(
- context,
- mediaCodecSelector,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- enableDecoderFallback,
- eventHandler,
- eventListener,
- audioSink);
- }
-
- /**
- * @param context A context.
- * @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
- * initialization fails. This may result in using a decoder that is slower/less efficient than
- * the primary decoder.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param audioSink The sink to which audio will be output.
- * @deprecated Use {@link #MediaCodecAudioRenderer(Context, MediaCodecSelector, boolean, Handler,
- * AudioRendererEventListener, AudioSink)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
- */
- @Deprecated
- public MediaCodecAudioRenderer(
- Context context,
- MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- boolean enableDecoderFallback,
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- AudioSink audioSink) {
super(
C.TRACK_TYPE_AUDIO,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
/* assumedMinimumCodecOperatingRate= */ 44100);
this.context = context.getApplicationContext();
this.audioSink = audioSink;
- lastInputTimeUs = C.TIME_UNSET;
- pendingStreamChangeTimesUs = new long[MAX_PENDING_STREAM_CHANGE_COUNT];
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioSink.setListener(new AudioSinkListener());
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
- protected int supportsFormat(
- MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- Format format)
+ protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
String mimeType = format.sampleMimeType;
if (!MimeTypes.isAudio(mimeType)) {
@@ -373,14 +216,8 @@
}
@TunnelingSupport
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
- boolean supportsFormatDrm =
- format.drmInitData == null
- || FrameworkMediaCrypto.class.equals(format.exoMediaCryptoType)
- || (format.exoMediaCryptoType == null
- && supportsFormatDrm(drmSessionManager, format.drmInitData));
- if (supportsFormatDrm
- && allowPassthrough(format.channelCount, mimeType)
- && mediaCodecSelector.getPassthroughDecoderInfo() != null) {
+ boolean supportsFormatDrm = supportsFormatDrm(format);
+ if (supportsFormatDrm && usePassthrough(format.channelCount, mimeType)) {
return RendererCapabilities.create(FORMAT_HANDLED, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
}
if ((MimeTypes.AUDIO_RAW.equals(mimeType)
@@ -418,12 +255,8 @@
if (mimeType == null) {
return Collections.emptyList();
}
- if (allowPassthrough(format.channelCount, mimeType)) {
- @Nullable
- MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
- if (passthroughDecoderInfo != null) {
- return Collections.singletonList(passthroughDecoderInfo);
- }
+ if (usePassthrough(format.channelCount, mimeType)) {
+ return Collections.singletonList(MediaCodecUtil.getPassthroughDecoderInfo());
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
@@ -442,16 +275,17 @@
/**
* Returns whether encoded audio passthrough should be used for playing back the input format.
- * This implementation returns true if the {@link AudioSink} indicates that encoded audio output
- * is supported.
*
* @param channelCount The number of channels in the input media, or {@link Format#NO_VALUE} if
* not known.
* @param mimeType The type of input media.
* @return Whether passthrough playback is supported.
+ * @throws DecoderQueryException If there was an error querying the available passthrough
+ * decoders.
*/
- protected boolean allowPassthrough(int channelCount, String mimeType) {
- return getPassthroughEncoding(channelCount, mimeType) != C.ENCODING_INVALID;
+ protected boolean usePassthrough(int channelCount, String mimeType) throws DecoderQueryException {
+ return getPassthroughEncoding(channelCount, mimeType) != C.ENCODING_INVALID
+ && MediaCodecUtil.getPassthroughDecoderInfo() != null;
}
@Override
@@ -464,10 +298,11 @@
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name);
- passthroughEnabled = codecInfo.passthrough;
- String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.codecMimeType;
+ passthroughEnabled =
+ MimeTypes.AUDIO_RAW.equals(codecInfo.mimeType)
+ && !MimeTypes.AUDIO_RAW.equals(format.sampleMimeType);
MediaFormat mediaFormat =
- getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
+ getMediaFormat(format, codecInfo.codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
@@ -610,6 +445,10 @@
*/
@C.Encoding
protected int getPassthroughEncoding(int channelCount, String mimeType) {
+ if (MimeTypes.AUDIO_RAW.equals(mimeType)) {
+ // PCM passthrough is not supported.
+ return C.ENCODING_INVALID;
+ }
if (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)) {
// E-AC3 JOC is object-based so the output channel count is arbitrary.
if (audioSink.supportsOutput(/* channelCount= */ Format.NO_VALUE, C.ENCODING_E_AC3_JOC)) {
@@ -633,30 +472,32 @@
* order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
* should be released in {@link #onDisabled()} (if not before).
*
- * @see AudioSink.Listener#onAudioSessionId(int)
+ * <p>See {@link AudioSink.Listener#onAudioSessionId(int)}.
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
}
- /**
- * @see AudioSink.Listener#onPositionDiscontinuity()
- */
+ /** See {@link AudioSink.Listener#onPositionDiscontinuity()}. */
protected void onAudioTrackPositionDiscontinuity() {
// Do nothing.
}
- /**
- * @see AudioSink.Listener#onUnderrun(int, long, long)
- */
- protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
- long elapsedSinceLastFeedMs) {
+ /** See {@link AudioSink.Listener#onUnderrun(int, long, long)}. */
+ protected void onAudioTrackUnderrun(
+ int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
+ // Do nothing.
+ }
+
+ /** See {@link AudioSink.Listener#onSkipSilenceEnabledChanged(boolean)}. */
+ protected void onAudioTrackSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
// Do nothing.
}
@Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- super.onEnabled(joining);
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters);
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
@@ -667,30 +508,12 @@
}
@Override
- protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
- super.onStreamChanged(formats, offsetUs);
- if (lastInputTimeUs != C.TIME_UNSET) {
- if (pendingStreamChangeCount == pendingStreamChangeTimesUs.length) {
- Log.w(
- TAG,
- "Too many stream changes, so dropping change at "
- + pendingStreamChangeTimesUs[pendingStreamChangeCount - 1]);
- } else {
- pendingStreamChangeCount++;
- }
- pendingStreamChangeTimesUs[pendingStreamChangeCount - 1] = lastInputTimeUs;
- }
- }
-
- @Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining);
audioSink.flush();
currentPositionUs = positionUs;
allowFirstBufferPositionDiscontinuity = true;
allowPositionDiscontinuity = true;
- lastInputTimeUs = C.TIME_UNSET;
- pendingStreamChangeCount = 0;
}
@Override
@@ -709,8 +532,6 @@
@Override
protected void onDisabled() {
try {
- lastInputTimeUs = C.TIME_UNSET;
- pendingStreamChangeCount = 0;
audioSink.flush();
} finally {
try {
@@ -749,13 +570,13 @@
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
- audioSink.setPlaybackParameters(playbackParameters);
+ public void setPlaybackSpeed(float playbackSpeed) {
+ audioSink.setPlaybackSpeed(playbackSpeed);
}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return audioSink.getPlaybackParameters();
+ public float getPlaybackSpeed() {
+ return audioSink.getPlaybackSpeed();
}
@Override
@@ -769,22 +590,12 @@
}
allowFirstBufferPositionDiscontinuity = false;
}
- lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs);
}
- @CallSuper
@Override
- protected void onProcessedOutputBuffer(long presentationTimeUs) {
- while (pendingStreamChangeCount != 0 && presentationTimeUs >= pendingStreamChangeTimesUs[0]) {
- audioSink.handleDiscontinuity();
- pendingStreamChangeCount--;
- System.arraycopy(
- pendingStreamChangeTimesUs,
- /* srcPos= */ 1,
- pendingStreamChangeTimesUs,
- /* destPos= */ 0,
- pendingStreamChangeCount);
- }
+ protected void onProcessedStreamChange() {
+ super.onProcessedStreamChange();
+ audioSink.handleDiscontinuity();
}
@Override
@@ -795,6 +606,7 @@
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
+ int sampleCount,
long bufferPresentationTimeUs,
boolean isDecodeOnlyBuffer,
boolean isLastBuffer,
@@ -803,8 +615,8 @@
if (codecNeedsEosBufferTimestampWorkaround
&& bufferPresentationTimeUs == 0
&& (bufferFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
- && lastInputTimeUs != C.TIME_UNSET) {
- bufferPresentationTimeUs = lastInputTimeUs;
+ && getLargestQueuedPresentationTimeUs() != C.TIME_UNSET) {
+ bufferPresentationTimeUs = getLargestQueuedPresentationTimeUs();
}
if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
@@ -820,16 +632,20 @@
return true;
}
+ boolean fullyConsumed;
try {
- if (audioSink.handleBuffer(buffer, bufferPresentationTimeUs)) {
- codec.releaseOutputBuffer(bufferIndex, false);
- decoderCounters.renderedOutputBufferCount++;
- return true;
- }
+ fullyConsumed = audioSink.handleBuffer(buffer, bufferPresentationTimeUs, sampleCount);
} catch (AudioSink.InitializationException | AudioSink.WriteException e) {
// TODO(internal: b/145658993) Use outputFormat instead.
throw createRendererException(e, inputFormat);
}
+
+ if (fullyConsumed) {
+ codec.releaseOutputBuffer(bufferIndex, false);
+ decoderCounters.renderedOutputBufferCount++;
+ return true;
+ }
+
return false;
}
@@ -857,6 +673,12 @@
AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
audioSink.setAuxEffectInfo(auxEffectInfo);
break;
+ case MSG_SET_SKIP_SILENCE_ENABLED:
+ audioSink.setSkipSilenceEnabled((Boolean) message);
+ break;
+ case MSG_SET_AUDIO_SESSION_ID:
+ audioSink.setAudioSessionId((Integer) message);
+ break;
default:
super.handleMessage(messageType, message);
break;
@@ -1031,6 +853,10 @@
onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
+ @Override
+ public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
+ eventDispatcher.skipSilenceEnabledChanged(skipSilenceEnabled);
+ onAudioTrackSkipSilenceEnabledChanged(skipSilenceEnabled);
+ }
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java
index 7175b93..883f5bc 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java
@@ -20,8 +20,17 @@
import java.nio.ByteBuffer;
/**
- * An {@link AudioProcessor} that converts 8-bit, 24-bit and 32-bit integer PCM audio to 16-bit
- * integer PCM audio.
+ * An {@link AudioProcessor} that converts different PCM audio encodings to 16-bit integer PCM. The
+ * following encodings are supported as input:
+ *
+ * <ul>
+ * <li>{@link C#ENCODING_PCM_8BIT}
+ * <li>{@link C#ENCODING_PCM_16BIT} ({@link #isActive()} will return {@code false})
+ * <li>{@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}
+ * <li>{@link C#ENCODING_PCM_24BIT}
+ * <li>{@link C#ENCODING_PCM_32BIT}
+ * <li>{@link C#ENCODING_PCM_FLOAT}
+ * </ul>
*/
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
@@ -33,7 +42,8 @@
&& encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_16BIT_BIG_ENDIAN
&& encoding != C.ENCODING_PCM_24BIT
- && encoding != C.ENCODING_PCM_32BIT) {
+ && encoding != C.ENCODING_PCM_32BIT
+ && encoding != C.ENCODING_PCM_FLOAT) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
return encoding != C.ENCODING_PCM_16BIT
@@ -60,10 +70,10 @@
resampledSize = (size / 3) * 2;
break;
case C.ENCODING_PCM_32BIT:
+ case C.ENCODING_PCM_FLOAT:
resampledSize = size / 2;
break;
case C.ENCODING_PCM_16BIT:
- case C.ENCODING_PCM_FLOAT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
@@ -101,8 +111,16 @@
buffer.put(inputBuffer.get(i + 3));
}
break;
- case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT:
+ // 32 bit floating point -> 16 bit resampling. Floating point values are in the range
+ // [-1.0, 1.0], so need to be scaled by Short.MAX_VALUE.
+ for (int i = position; i < limit; i += 4) {
+ short value = (short) (inputBuffer.getFloat(i) * Short.MAX_VALUE);
+ buffer.put((byte) (value & 0xFF));
+ buffer.put((byte) ((value >> 8) & 0xFF));
+ }
+ break;
+ case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java
deleted file mode 100644
index 2f324db..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.audio;
-
-import android.media.audiofx.Virtualizer;
-import android.os.Handler;
-import android.os.SystemClock;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.BaseRenderer;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayer;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.PlayerMessage.Target;
-import com.google.android.exoplayer2.RendererCapabilities;
-import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
-import com.google.android.exoplayer2.decoder.DecoderCounters;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.drm.DrmSession;
-import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.MediaClock;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.util.TraceUtil;
-import com.google.android.exoplayer2.util.Util;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Decodes and renders audio using a {@link SimpleDecoder}.
- *
- * <p>This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)}
- * on the playback thread:
- *
- * <ul>
- * <li>Message with type {@link #MSG_SET_VOLUME} to set the volume. The message payload should be
- * a {@link Float} with 0 being silence and 1 being unity gain.
- * <li>Message with type {@link #MSG_SET_AUDIO_ATTRIBUTES} to set the audio attributes. The
- * message payload should be an {@link com.google.android.exoplayer2.audio.AudioAttributes}
- * instance that will configure the underlying audio track.
- * <li>Message with type {@link #MSG_SET_AUX_EFFECT_INFO} to set the auxiliary effect. The message
- * payload should be an {@link AuxEffectInfo} instance that will configure the underlying
- * audio track.
- * </ul>
- */
-public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock {
-
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- REINITIALIZATION_STATE_NONE,
- REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
- REINITIALIZATION_STATE_WAIT_END_OF_STREAM
- })
- private @interface ReinitializationState {}
- /**
- * The decoder does not need to be re-initialized.
- */
- private static final int REINITIALIZATION_STATE_NONE = 0;
- /**
- * The input format has changed in a way that requires the decoder to be re-initialized, but we
- * haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
- * ensure that it outputs any remaining buffers before we release it.
- */
- private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
- /**
- * The input format has changed in a way that requires the decoder to be re-initialized, and we've
- * signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
- * end of stream signal to indicate that it has output any remaining buffers before we release it.
- */
- private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
-
- private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
- private final boolean playClearSamplesWithoutKeys;
- private final EventDispatcher eventDispatcher;
- private final AudioSink audioSink;
- private final DecoderInputBuffer flagsOnlyBuffer;
-
- private DecoderCounters decoderCounters;
- private Format inputFormat;
- private int encoderDelay;
- private int encoderPadding;
-
- @Nullable
- private SimpleDecoder<
- DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException>
- decoder;
-
- @Nullable private DecoderInputBuffer inputBuffer;
- @Nullable private SimpleOutputBuffer outputBuffer;
- @Nullable private DrmSession<ExoMediaCrypto> decoderDrmSession;
- @Nullable private DrmSession<ExoMediaCrypto> sourceDrmSession;
-
- @ReinitializationState private int decoderReinitializationState;
- private boolean decoderReceivedBuffers;
- private boolean audioTrackNeedsConfigure;
-
- private long currentPositionUs;
- private boolean allowFirstBufferPositionDiscontinuity;
- private boolean allowPositionDiscontinuity;
- private boolean inputStreamEnded;
- private boolean outputStreamEnded;
- private boolean waitingForKeys;
-
- public SimpleDecoderAudioRenderer() {
- this(/* eventHandler= */ null, /* eventListener= */ null);
- }
-
- /**
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
- */
- public SimpleDecoderAudioRenderer(
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- AudioProcessor... audioProcessors) {
- this(
- eventHandler,
- eventListener,
- /* audioCapabilities= */ null,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- audioProcessors);
- }
-
- /**
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param audioCapabilities The audio capabilities for playback on this device. May be null if the
- * default capabilities (no encoded audio passthrough support) should be assumed.
- */
- public SimpleDecoderAudioRenderer(
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- @Nullable AudioCapabilities audioCapabilities) {
- this(
- eventHandler,
- eventListener,
- audioCapabilities,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false);
- }
-
- /**
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param audioCapabilities The audio capabilities for playback on this device. May be null if the
- * default capabilities (no encoded audio passthrough support) should be assumed.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
- */
- public SimpleDecoderAudioRenderer(
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- @Nullable AudioCapabilities audioCapabilities,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- AudioProcessor... audioProcessors) {
- this(eventHandler, eventListener, drmSessionManager,
- playClearSamplesWithoutKeys, new DefaultAudioSink(audioCapabilities, audioProcessors));
- }
-
- /**
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param audioSink The sink to which audio will be output.
- */
- public SimpleDecoderAudioRenderer(
- @Nullable Handler eventHandler,
- @Nullable AudioRendererEventListener eventListener,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- AudioSink audioSink) {
- super(C.TRACK_TYPE_AUDIO);
- this.drmSessionManager = drmSessionManager;
- this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
- eventDispatcher = new EventDispatcher(eventHandler, eventListener);
- this.audioSink = audioSink;
- audioSink.setListener(new AudioSinkListener());
- flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
- decoderReinitializationState = REINITIALIZATION_STATE_NONE;
- audioTrackNeedsConfigure = true;
- }
-
- @Override
- @Nullable
- public MediaClock getMediaClock() {
- return this;
- }
-
- @Override
- @Capabilities
- public final int supportsFormat(Format format) {
- if (!MimeTypes.isAudio(format.sampleMimeType)) {
- return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
- }
- @FormatSupport int formatSupport = supportsFormatInternal(drmSessionManager, format);
- if (formatSupport <= FORMAT_UNSUPPORTED_DRM) {
- return RendererCapabilities.create(formatSupport);
- }
- @TunnelingSupport
- int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
- return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
- }
-
- /**
- * Returns the {@link FormatSupport} for the given {@link Format}.
- *
- * @param drmSessionManager The renderer's {@link DrmSessionManager}.
- * @param format The format, which has an audio {@link Format#sampleMimeType}.
- * @return The {@link FormatSupport} for this {@link Format}.
- */
- @FormatSupport
- protected abstract int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format);
-
- /**
- * Returns whether the sink supports the audio format.
- *
- * @see AudioSink#supportsOutput(int, int)
- */
- protected final boolean supportsOutput(int channelCount, @C.Encoding int encoding) {
- return audioSink.supportsOutput(channelCount, encoding);
- }
-
- @Override
- public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- if (outputStreamEnded) {
- try {
- audioSink.playToEndOfStream();
- } catch (AudioSink.WriteException e) {
- throw createRendererException(e, inputFormat);
- }
- return;
- }
-
- // Try and read a format if we don't have one already.
- if (inputFormat == null) {
- // We don't have a format yet, so try and read one.
- FormatHolder formatHolder = getFormatHolder();
- flagsOnlyBuffer.clear();
- int result = readSource(formatHolder, flagsOnlyBuffer, true);
- if (result == C.RESULT_FORMAT_READ) {
- onInputFormatChanged(formatHolder);
- } else if (result == C.RESULT_BUFFER_READ) {
- // End of stream read having not read a format.
- Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
- inputStreamEnded = true;
- processEndOfStream();
- return;
- } else {
- // We still don't have a format and can't make progress without one.
- return;
- }
- }
-
- // If we don't have a decoder yet, we need to instantiate one.
- maybeInitDecoder();
-
- if (decoder != null) {
- try {
- // Rendering loop.
- TraceUtil.beginSection("drainAndFeed");
- while (drainOutputBuffer()) {}
- while (feedInputBuffer()) {}
- TraceUtil.endSection();
- } catch (AudioDecoderException | AudioSink.ConfigurationException
- | AudioSink.InitializationException | AudioSink.WriteException e) {
- throw createRendererException(e, inputFormat);
- }
- decoderCounters.ensureUpdated();
- }
- }
-
- /**
- * Called when the audio session id becomes known. The default implementation is a no-op. One
- * reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in
- * order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
- * should be released in {@link #onDisabled()} (if not before).
- *
- * @see AudioSink.Listener#onAudioSessionId(int)
- */
- protected void onAudioSessionId(int audioSessionId) {
- // Do nothing.
- }
-
- /**
- * @see AudioSink.Listener#onPositionDiscontinuity()
- */
- protected void onAudioTrackPositionDiscontinuity() {
- // Do nothing.
- }
-
- /**
- * @see AudioSink.Listener#onUnderrun(int, long, long)
- */
- protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
- long elapsedSinceLastFeedMs) {
- // Do nothing.
- }
-
- /**
- * Creates a decoder for the given format.
- *
- * @param format The format for which a decoder is required.
- * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
- * Maybe null and can be ignored if decoder does not handle encrypted content.
- * @return The decoder.
- * @throws AudioDecoderException If an error occurred creating a suitable decoder.
- */
- protected abstract SimpleDecoder<
- DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException>
- createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
- throws AudioDecoderException;
-
- /**
- * Returns the format of audio buffers output by the decoder. Will not be called until the first
- * output buffer has been dequeued, so the decoder may use input data to determine the format.
- */
- protected abstract Format getOutputFormat();
-
- /**
- * Returns whether the existing decoder can be kept for a new format.
- *
- * @param oldFormat The previous format.
- * @param newFormat The new format.
- * @return True if the existing decoder can be kept.
- */
- protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
- return false;
- }
-
- private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
- AudioSink.ConfigurationException, AudioSink.InitializationException,
- AudioSink.WriteException {
- if (outputBuffer == null) {
- outputBuffer = decoder.dequeueOutputBuffer();
- if (outputBuffer == null) {
- return false;
- }
- if (outputBuffer.skippedOutputBufferCount > 0) {
- decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
- audioSink.handleDiscontinuity();
- }
- }
-
- if (outputBuffer.isEndOfStream()) {
- if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
- // We're waiting to re-initialize the decoder, and have now processed all final buffers.
- releaseDecoder();
- maybeInitDecoder();
- // The audio track may need to be recreated once the new output format is known.
- audioTrackNeedsConfigure = true;
- } else {
- outputBuffer.release();
- outputBuffer = null;
- processEndOfStream();
- }
- return false;
- }
-
- if (audioTrackNeedsConfigure) {
- Format outputFormat = getOutputFormat();
- audioSink.configure(outputFormat.pcmEncoding, outputFormat.channelCount,
- outputFormat.sampleRate, 0, null, encoderDelay, encoderPadding);
- audioTrackNeedsConfigure = false;
- }
-
- if (audioSink.handleBuffer(outputBuffer.data, outputBuffer.timeUs)) {
- decoderCounters.renderedOutputBufferCount++;
- outputBuffer.release();
- outputBuffer = null;
- return true;
- }
-
- return false;
- }
-
- private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException {
- if (decoder == null || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
- || inputStreamEnded) {
- // We need to reinitialize the decoder or the input stream has ended.
- return false;
- }
-
- if (inputBuffer == null) {
- inputBuffer = decoder.dequeueInputBuffer();
- if (inputBuffer == null) {
- return false;
- }
- }
-
- if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
- inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
- decoder.queueInputBuffer(inputBuffer);
- inputBuffer = null;
- decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
- return false;
- }
-
- int result;
- FormatHolder formatHolder = getFormatHolder();
- if (waitingForKeys) {
- // We've already read an encrypted sample into buffer, and are waiting for keys.
- result = C.RESULT_BUFFER_READ;
- } else {
- result = readSource(formatHolder, inputBuffer, false);
- }
-
- if (result == C.RESULT_NOTHING_READ) {
- return false;
- }
- if (result == C.RESULT_FORMAT_READ) {
- onInputFormatChanged(formatHolder);
- return true;
- }
- if (inputBuffer.isEndOfStream()) {
- inputStreamEnded = true;
- decoder.queueInputBuffer(inputBuffer);
- inputBuffer = null;
- return false;
- }
- boolean bufferEncrypted = inputBuffer.isEncrypted();
- waitingForKeys = shouldWaitForKeys(bufferEncrypted);
- if (waitingForKeys) {
- return false;
- }
- inputBuffer.flip();
- onQueueInputBuffer(inputBuffer);
- decoder.queueInputBuffer(inputBuffer);
- decoderReceivedBuffers = true;
- decoderCounters.inputBufferCount++;
- inputBuffer = null;
- return true;
- }
-
- private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
- if (decoderDrmSession == null
- || (!bufferEncrypted
- && (playClearSamplesWithoutKeys || decoderDrmSession.playClearSamplesWithoutKeys()))) {
- return false;
- }
- @DrmSession.State int drmSessionState = decoderDrmSession.getState();
- if (drmSessionState == DrmSession.STATE_ERROR) {
- throw createRendererException(decoderDrmSession.getError(), inputFormat);
- }
- return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
- }
-
- private void processEndOfStream() throws ExoPlaybackException {
- outputStreamEnded = true;
- try {
- audioSink.playToEndOfStream();
- } catch (AudioSink.WriteException e) {
- // TODO(internal: b/145658993) Use outputFormat for the call from drainOutputBuffer.
- throw createRendererException(e, inputFormat);
- }
- }
-
- private void flushDecoder() throws ExoPlaybackException {
- waitingForKeys = false;
- if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
- releaseDecoder();
- maybeInitDecoder();
- } else {
- inputBuffer = null;
- if (outputBuffer != null) {
- outputBuffer.release();
- outputBuffer = null;
- }
- decoder.flush();
- decoderReceivedBuffers = false;
- }
- }
-
- @Override
- public boolean isEnded() {
- return outputStreamEnded && audioSink.isEnded();
- }
-
- @Override
- public boolean isReady() {
- return audioSink.hasPendingData()
- || (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null));
- }
-
- @Override
- public long getPositionUs() {
- if (getState() == STATE_STARTED) {
- updateCurrentPosition();
- }
- return currentPositionUs;
- }
-
- @Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
- audioSink.setPlaybackParameters(playbackParameters);
- }
-
- @Override
- public PlaybackParameters getPlaybackParameters() {
- return audioSink.getPlaybackParameters();
- }
-
- @Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- decoderCounters = new DecoderCounters();
- eventDispatcher.enabled(decoderCounters);
- int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
- if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
- audioSink.enableTunnelingV21(tunnelingAudioSessionId);
- } else {
- audioSink.disableTunneling();
- }
- }
-
- @Override
- protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
- audioSink.flush();
- currentPositionUs = positionUs;
- allowFirstBufferPositionDiscontinuity = true;
- allowPositionDiscontinuity = true;
- inputStreamEnded = false;
- outputStreamEnded = false;
- if (decoder != null) {
- flushDecoder();
- }
- }
-
- @Override
- protected void onStarted() {
- audioSink.play();
- }
-
- @Override
- protected void onStopped() {
- updateCurrentPosition();
- audioSink.pause();
- }
-
- @Override
- protected void onDisabled() {
- inputFormat = null;
- audioTrackNeedsConfigure = true;
- waitingForKeys = false;
- try {
- setSourceDrmSession(null);
- releaseDecoder();
- audioSink.reset();
- } finally {
- eventDispatcher.disabled(decoderCounters);
- }
- }
-
- @Override
- public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SET_VOLUME:
- audioSink.setVolume((Float) message);
- break;
- case MSG_SET_AUDIO_ATTRIBUTES:
- AudioAttributes audioAttributes = (AudioAttributes) message;
- audioSink.setAudioAttributes(audioAttributes);
- break;
- case MSG_SET_AUX_EFFECT_INFO:
- AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
- audioSink.setAuxEffectInfo(auxEffectInfo);
- break;
- default:
- super.handleMessage(messageType, message);
- break;
- }
- }
-
- private void maybeInitDecoder() throws ExoPlaybackException {
- if (decoder != null) {
- return;
- }
-
- setDecoderDrmSession(sourceDrmSession);
-
- ExoMediaCrypto mediaCrypto = null;
- if (decoderDrmSession != null) {
- mediaCrypto = decoderDrmSession.getMediaCrypto();
- if (mediaCrypto == null) {
- DrmSessionException drmError = decoderDrmSession.getError();
- if (drmError != null) {
- // Continue for now. We may be able to avoid failure if the session recovers, or if a new
- // input format causes the session to be replaced before it's used.
- } else {
- // The drm session isn't open yet.
- return;
- }
- }
- }
-
- try {
- long codecInitializingTimestamp = SystemClock.elapsedRealtime();
- TraceUtil.beginSection("createAudioDecoder");
- decoder = createDecoder(inputFormat, mediaCrypto);
- TraceUtil.endSection();
- long codecInitializedTimestamp = SystemClock.elapsedRealtime();
- eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
- codecInitializedTimestamp - codecInitializingTimestamp);
- decoderCounters.decoderInitCount++;
- } catch (AudioDecoderException e) {
- throw createRendererException(e, inputFormat);
- }
- }
-
- private void releaseDecoder() {
- inputBuffer = null;
- outputBuffer = null;
- decoderReinitializationState = REINITIALIZATION_STATE_NONE;
- decoderReceivedBuffers = false;
- if (decoder != null) {
- decoder.release();
- decoder = null;
- decoderCounters.decoderReleaseCount++;
- }
- setDecoderDrmSession(null);
- }
-
- private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
- DrmSession.replaceSession(sourceDrmSession, session);
- sourceDrmSession = session;
- }
-
- private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
- DrmSession.replaceSession(decoderDrmSession, session);
- decoderDrmSession = session;
- }
-
- @SuppressWarnings("unchecked")
- private void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
- Format newFormat = Assertions.checkNotNull(formatHolder.format);
- if (formatHolder.includesDrmSession) {
- setSourceDrmSession((DrmSession<ExoMediaCrypto>) formatHolder.drmSession);
- } else {
- sourceDrmSession =
- getUpdatedSourceDrmSession(inputFormat, newFormat, drmSessionManager, sourceDrmSession);
- }
- Format oldFormat = inputFormat;
- inputFormat = newFormat;
-
- if (!canKeepCodec(oldFormat, inputFormat)) {
- if (decoderReceivedBuffers) {
- // Signal end of stream and wait for any final output buffers before re-initialization.
- decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
- } else {
- // There aren't any final output buffers, so release the decoder immediately.
- releaseDecoder();
- maybeInitDecoder();
- audioTrackNeedsConfigure = true;
- }
- }
-
- encoderDelay = inputFormat.encoderDelay;
- encoderPadding = inputFormat.encoderPadding;
-
- eventDispatcher.inputFormatChanged(inputFormat);
- }
-
- private void onQueueInputBuffer(DecoderInputBuffer buffer) {
- if (allowFirstBufferPositionDiscontinuity && !buffer.isDecodeOnly()) {
- // TODO: Remove this hack once we have a proper fix for [Internal: b/71876314].
- // Allow the position to jump if the first presentable input buffer has a timestamp that
- // differs significantly from what was expected.
- if (Math.abs(buffer.timeUs - currentPositionUs) > 500000) {
- currentPositionUs = buffer.timeUs;
- }
- allowFirstBufferPositionDiscontinuity = false;
- }
- }
-
- private void updateCurrentPosition() {
- long newCurrentPositionUs = audioSink.getCurrentPositionUs(isEnded());
- if (newCurrentPositionUs != AudioSink.CURRENT_POSITION_NOT_SET) {
- currentPositionUs =
- allowPositionDiscontinuity
- ? newCurrentPositionUs
- : Math.max(currentPositionUs, newCurrentPositionUs);
- allowPositionDiscontinuity = false;
- }
- }
-
- private final class AudioSinkListener implements AudioSink.Listener {
-
- @Override
- public void onAudioSessionId(int audioSessionId) {
- eventDispatcher.audioSessionId(audioSessionId);
- SimpleDecoderAudioRenderer.this.onAudioSessionId(audioSessionId);
- }
-
- @Override
- public void onPositionDiscontinuity() {
- onAudioTrackPositionDiscontinuity();
- // We are out of sync so allow currentPositionUs to jump backwards.
- SimpleDecoderAudioRenderer.this.allowPositionDiscontinuity = true;
- }
-
- @Override
- public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
- eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
- onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
- }
-
- }
-
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
index 6cd46bb..50e4240 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
@@ -35,7 +35,6 @@
private final int inputSampleRateHz;
private final int channelCount;
private final float speed;
- private final float pitch;
private final float rate;
private final int minPeriod;
private final int maxPeriod;
@@ -62,15 +61,12 @@
* @param inputSampleRateHz The sample rate of input audio, in hertz.
* @param channelCount The number of channels in the input audio.
* @param speed The speedup factor for output audio.
- * @param pitch The pitch factor for output audio.
* @param outputSampleRateHz The sample rate for output audio, in hertz.
*/
- public Sonic(
- int inputSampleRateHz, int channelCount, float speed, float pitch, int outputSampleRateHz) {
+ public Sonic(int inputSampleRateHz, int channelCount, float speed, int outputSampleRateHz) {
this.inputSampleRateHz = inputSampleRateHz;
this.channelCount = channelCount;
this.speed = speed;
- this.pitch = pitch;
rate = (float) inputSampleRateHz / outputSampleRateHz;
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
@@ -120,10 +116,8 @@
*/
public void queueEndOfStream() {
int remainingFrameCount = inputFrameCount;
- float s = speed / pitch;
- float r = rate * pitch;
int expectedOutputFrames =
- outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f);
+ outputFrameCount + (int) ((remainingFrameCount / speed + pitchFrameCount) / rate + 0.5f);
// Add enough silence to flush both input and pitch buffers.
inputBuffer =
@@ -468,16 +462,14 @@
private void processStreamInput() {
// Resample as many pitch periods as we have buffered on the input.
int originalOutputFrameCount = outputFrameCount;
- float s = speed / pitch;
- float r = rate * pitch;
- if (s > 1.00001 || s < 0.99999) {
- changeSpeed(s);
+ if (speed > 1.00001 || speed < 0.99999) {
+ changeSpeed(speed);
} else {
copyToOutput(inputBuffer, 0, inputFrameCount);
inputFrameCount = 0;
}
- if (r != 1.0f) {
- adjustRate(r, originalOutputFrameCount);
+ if (rate != 1.0f) {
+ adjustRate(rate, originalOutputFrameCount);
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
index b9a59cd..48075ba 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
@@ -38,14 +38,6 @@
*/
public static final float MINIMUM_SPEED = 0.1f;
/**
- * The maximum allowed pitch in {@link #setPitch(float)}.
- */
- public static final float MAXIMUM_PITCH = 8.0f;
- /**
- * The minimum allowed pitch in {@link #setPitch(float)}.
- */
- public static final float MINIMUM_PITCH = 0.1f;
- /**
* Indicates that the output sample rate should be the same as the input.
*/
public static final int SAMPLE_RATE_NO_CHANGE = -1;
@@ -63,7 +55,6 @@
private int pendingOutputSampleRate;
private float speed;
- private float pitch;
private AudioFormat pendingInputAudioFormat;
private AudioFormat pendingOutputAudioFormat;
@@ -84,7 +75,6 @@
*/
public SonicAudioProcessor() {
speed = 1f;
- pitch = 1f;
pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET;
inputAudioFormat = AudioFormat.NOT_SET;
@@ -113,23 +103,6 @@
}
/**
- * Sets the playback pitch. This method may only be called after draining data through the
- * processor. The value returned by {@link #isActive()} may change, and the processor must be
- * {@link #flush() flushed} before queueing more data.
- *
- * @param pitch The requested new pitch.
- * @return The actual new pitch.
- */
- public float setPitch(float pitch) {
- pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
- if (this.pitch != pitch) {
- this.pitch = pitch;
- pendingSonicRecreation = true;
- }
- return pitch;
- }
-
- /**
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
* audio at the same sample rate as the input. After calling this method, call {@link
* #configure(AudioFormat)} to configure the processor with the new sample rate.
@@ -182,7 +155,6 @@
public boolean isActive() {
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
- || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
}
@@ -243,7 +215,6 @@
inputAudioFormat.sampleRate,
inputAudioFormat.channelCount,
speed,
- pitch,
outputAudioFormat.sampleRate);
} else if (sonic != null) {
sonic.flush();
@@ -258,7 +229,6 @@
@Override
public void reset() {
speed = 1f;
- pitch = 1f;
pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET;
inputAudioFormat = AudioFormat.NOT_SET;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java
index 9437e4a..8d84325 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java
@@ -155,15 +155,16 @@
@Override
protected void onFlush() {
if (reconfigurationPending) {
+ // This is the initial flush after reconfiguration. Prepare to trim bytes from the start/end.
reconfigurationPending = false;
endBuffer = new byte[trimEndFrames * inputAudioFormat.bytesPerFrame];
pendingTrimStartBytes = trimStartFrames * inputAudioFormat.bytesPerFrame;
} else {
- // Audio processors are flushed after initial configuration, so we leave the pending trim
- // start byte count unmodified if the processor was just configured. Otherwise we (possibly
- // incorrectly) assume that this is a seek to a non-zero position. We should instead check the
- // timestamp of the first input buffer queued after flushing to decide whether to trim (see
- // also [Internal: b/77292509]).
+ // This is a flush during playback (after the initial flush). We assume this was caused by a
+ // seek to a non-zero position and clear pending start bytes. This assumption may be wrong (we
+ // may be seeking to zero), but playing data that should have been trimmed shouldn't be
+ // noticeable after a seek. Ideally we would check the timestamp of the first input buffer
+ // queued after flushing to decide whether to trim (see also [Internal: b/77292509]).
pendingTrimStartBytes = 0;
}
endBufferSize = 0;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java
index be367d2..f1d269d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java
@@ -40,6 +40,8 @@
public static final int FEATURE_CACHE_CONTENT_METADATA = 1;
/** Version of tables used for cache file metadata. */
public static final int FEATURE_CACHE_FILE_METADATA = 2;
+ /** Version of tables used from external features. */
+ public static final int FEATURE_EXTERNAL = 1000;
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions";
@@ -67,7 +69,12 @@
@Documented
@Retention(RetentionPolicy.SOURCE)
- @IntDef({FEATURE_OFFLINE, FEATURE_CACHE_CONTENT_METADATA, FEATURE_CACHE_FILE_METADATA})
+ @IntDef({
+ FEATURE_OFFLINE,
+ FEATURE_CACHE_CONTENT_METADATA,
+ FEATURE_CACHE_FILE_METADATA,
+ FEATURE_EXTERNAL
+ })
private @interface Feature {}
private VersionTable() {}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java
index 8409bab..5de4fcb 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java
@@ -73,6 +73,23 @@
* dropped from the source to advance to the keyframe.
*/
public int droppedToKeyframeCount;
+ /**
+ * The sum of video frame processing offset samples in microseconds.
+ *
+ * <p>Video frame processing offset measures how early a video frame was processed by a video
+ * renderer compared to the player's current position.
+ *
+ * <p>Note: Use {@link #addVideoFrameProcessingOffsetSample(long)} to update this field instead of
+ * updating it directly.
+ */
+ public long totalVideoFrameProcessingOffsetUs;
+ /**
+ * The number of video frame processing offset samples added.
+ *
+ * <p>Note: Use {@link #addVideoFrameProcessingOffsetSample(long)} to update this field instead of
+ * updating it directly.
+ */
+ public int videoFrameProcessingOffsetCount;
/**
* Should be called to ensure counter values are made visible across threads. The playback thread
@@ -100,6 +117,25 @@
maxConsecutiveDroppedBufferCount = Math.max(maxConsecutiveDroppedBufferCount,
other.maxConsecutiveDroppedBufferCount);
droppedToKeyframeCount += other.droppedToKeyframeCount;
+
+ addVideoFrameProcessingOffsetSamples(
+ other.totalVideoFrameProcessingOffsetUs, other.videoFrameProcessingOffsetCount);
}
+ /**
+ * Adds a video frame processing offset sample to {@link #totalVideoFrameProcessingOffsetUs} and
+ * increases {@link #videoFrameProcessingOffsetCount} by one.
+ *
+ * <p>Convenience method to ensure both fields are updated when adding a sample.
+ *
+ * @param sampleUs The sample in microseconds.
+ */
+ public void addVideoFrameProcessingOffsetSample(long sampleUs) {
+ addVideoFrameProcessingOffsetSamples(sampleUs, /* count= */ 1);
+ }
+
+ private void addVideoFrameProcessingOffsetSamples(long sampleUs, int count) {
+ totalVideoFrameProcessingOffsetUs += sampleUs;
+ videoFrameProcessingOffsetCount += count;
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderException.java
new file mode 100644
index 0000000..c07e646
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.decoder;
+
+/** Thrown when a {@link Decoder} error occurs. */
+public class DecoderException extends Exception {
+
+ /** @param message The detail message for this exception. */
+ public DecoderException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message The detail message for this exception.
+ * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
+ * A <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or unknown.
+ */
+ public DecoderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java
index 730ce15..ca431bf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java
@@ -20,6 +20,17 @@
*/
public abstract class OutputBuffer extends Buffer {
+ /** Buffer owner. */
+ public interface Owner<S extends OutputBuffer> {
+
+ /**
+ * Releases the buffer.
+ *
+ * @param outputBuffer Output buffer.
+ */
+ void releaseOutputBuffer(S outputBuffer);
+ }
+
/**
* The presentation timestamp for the buffer, in microseconds.
*/
@@ -34,5 +45,4 @@
* Releases the output buffer for reuse. Must be called when the buffer is no longer needed.
*/
public abstract void release();
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java
index 6dbf9f5..8f660c4 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java
@@ -21,7 +21,10 @@
import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayDeque;
-/** Base class for {@link Decoder}s that use their own decode thread. */
+/**
+ * Base class for {@link Decoder}s that use their own decode thread and decode each input buffer
+ * immediately into a corresponding output buffer.
+ */
@SuppressWarnings("UngroupedOverloads")
public abstract class SimpleDecoder<
I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception>
@@ -63,7 +66,7 @@
availableOutputBuffers[i] = createOutputBuffer();
}
decodeThread =
- new Thread("SimpleDecoder:Decode") {
+ new Thread("ExoPlayer:SimpleDecoder") {
@Override
public void run() {
SimpleDecoder.this.run();
@@ -150,6 +153,7 @@
while (!queuedOutputBuffers.isEmpty()) {
queuedOutputBuffers.removeFirst().release();
}
+ exception = null;
}
}
@@ -226,6 +230,7 @@
if (inputBuffer.isDecodeOnly()) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
+ @Nullable E exception;
try {
exception = decode(inputBuffer, outputBuffer, resetDecoder);
} catch (RuntimeException e) {
@@ -239,8 +244,9 @@
exception = createUnexpectedDecodeException(e);
}
if (exception != null) {
- // Memory barrier to ensure that the decoder exception is visible from the playback thread.
- synchronized (lock) {}
+ synchronized (lock) {
+ this.exception = exception;
+ }
return false;
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java
index 84cffc1..22cff02 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java
@@ -24,11 +24,11 @@
*/
public class SimpleOutputBuffer extends OutputBuffer {
- private final SimpleDecoder<?, SimpleOutputBuffer, ?> owner;
+ private final Owner<SimpleOutputBuffer> owner;
@Nullable public ByteBuffer data;
- public SimpleOutputBuffer(SimpleDecoder<?, SimpleOutputBuffer, ?> owner) {
+ public SimpleOutputBuffer(Owner<SimpleOutputBuffer> owner) {
this.owner = owner;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java
new file mode 100644
index 0000000..43c3702
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.device;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Information about the playback device. */
+public final class DeviceInfo {
+
+ /** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @IntDef({
+ PLAYBACK_TYPE_LOCAL,
+ PLAYBACK_TYPE_REMOTE,
+ })
+ public @interface PlaybackType {}
+ /** Playback happens on the local device (e.g. phone). */
+ public static final int PLAYBACK_TYPE_LOCAL = 0;
+ /** Playback happens outside of the device (e.g. a cast device). */
+ public static final int PLAYBACK_TYPE_REMOTE = 1;
+
+ /** The type of playback. */
+ public final @PlaybackType int playbackType;
+ /** The minimum volume that the device supports. */
+ public final int minVolume;
+ /** The maximum volume that the device supports. */
+ public final int maxVolume;
+
+ /** Creates device information. */
+ public DeviceInfo(@PlaybackType int playbackType, int minVolume, int maxVolume) {
+ this.playbackType = playbackType;
+ this.minVolume = minVolume;
+ this.maxVolume = maxVolume;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DeviceInfo)) {
+ return false;
+ }
+ DeviceInfo other = (DeviceInfo) obj;
+ return playbackType == other.playbackType
+ && minVolume == other.minVolume
+ && maxVolume == other.maxVolume;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + playbackType;
+ result = 31 * result + minVolume;
+ result = 31 * result + maxVolume;
+ return result;
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java
new file mode 100644
index 0000000..f310b6d
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.device;
+
+import com.google.android.exoplayer2.Player;
+
+/** A listener for changes of {@link Player.DeviceComponent}. */
+public interface DeviceListener {
+
+ /** Called when the device information changes. */
+ default void onDeviceInfoChanged(DeviceInfo deviceInfo) {}
+
+ /** Called when the device volume changes. */
+ default void onDeviceVolumeChanged(int volume) {}
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/device/package-info.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/package-info.java
new file mode 100644
index 0000000..400a2e1
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+package com.google.android.exoplayer2.device;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java
index b0a2d78..ad8a5c9 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.drm;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.media.NotProvisionedException;
import android.os.Handler;
import android.os.HandlerThread;
@@ -25,14 +24,16 @@
import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.EventDispatcher;
+import com.google.android.exoplayer2.util.CopyOnWriteMultiset;
import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.Arrays;
@@ -46,8 +47,8 @@
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** A {@link DrmSession} that supports playbacks using {@link ExoMediaDrm}. */
-@TargetApi(18)
-/* package */ class DefaultDrmSession<T extends ExoMediaCrypto> implements DrmSession<T> {
+@RequiresApi(18)
+/* package */ class DefaultDrmSession implements DrmSession {
/** Thrown when an unexpected exception or error is thrown during provisioning or key requests. */
public static final class UnexpectedDrmSessionException extends IOException {
@@ -58,7 +59,7 @@
}
/** Manages provisioning requests. */
- public interface ProvisioningManager<T extends ExoMediaCrypto> {
+ public interface ProvisioningManager {
/**
* Called when a session requires provisioning. The manager <em>may</em> call {@link
@@ -68,7 +69,7 @@
*
* @param session The session.
*/
- void provisionRequired(DefaultDrmSession<T> session);
+ void provisionRequired(DefaultDrmSession session);
/**
* Called by a session when it fails to perform a provisioning operation.
@@ -82,14 +83,14 @@
}
/** Callback to be notified when the session is released. */
- public interface ReleaseCallback<T extends ExoMediaCrypto> {
+ public interface ReleaseCallback {
/**
* Called immediately after releasing session resources.
*
* @param session The session.
*/
- void onSessionReleased(DefaultDrmSession<T> session);
+ void onSessionReleased(DefaultDrmSession session);
}
private static final String TAG = "DefaultDrmSession";
@@ -101,14 +102,14 @@
/** The DRM scheme datas, or null if this session uses offline keys. */
@Nullable public final List<SchemeData> schemeDatas;
- private final ExoMediaDrm<T> mediaDrm;
- private final ProvisioningManager<T> provisioningManager;
- private final ReleaseCallback<T> releaseCallback;
+ private final ExoMediaDrm mediaDrm;
+ private final ProvisioningManager provisioningManager;
+ private final ReleaseCallback releaseCallback;
private final @DefaultDrmSessionManager.Mode int mode;
private final boolean playClearSamplesWithoutKeys;
private final boolean isPlaceholderSession;
private final HashMap<String, String> keyRequestParameters;
- private final EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher;
+ private final CopyOnWriteMultiset<MediaSourceEventDispatcher> eventDispatchers;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
/* package */ final MediaDrmCallback callback;
@@ -119,7 +120,7 @@
private int referenceCount;
@Nullable private HandlerThread requestHandlerThread;
@Nullable private RequestHandler requestHandler;
- @Nullable private T mediaCrypto;
+ @Nullable private ExoMediaCrypto mediaCrypto;
@Nullable private DrmSessionException lastException;
@Nullable private byte[] sessionId;
private byte @MonotonicNonNull [] offlineLicenseKeySetId;
@@ -143,15 +144,14 @@
* @param keyRequestParameters Key request parameters.
* @param callback The media DRM callback.
* @param playbackLooper The playback looper.
- * @param eventDispatcher The dispatcher for DRM session manager events.
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for key and provisioning
* requests.
*/
public DefaultDrmSession(
UUID uuid,
- ExoMediaDrm<T> mediaDrm,
- ProvisioningManager<T> provisioningManager,
- ReleaseCallback<T> releaseCallback,
+ ExoMediaDrm mediaDrm,
+ ProvisioningManager provisioningManager,
+ ReleaseCallback releaseCallback,
@Nullable List<SchemeData> schemeDatas,
@DefaultDrmSessionManager.Mode int mode,
boolean playClearSamplesWithoutKeys,
@@ -160,7 +160,6 @@
HashMap<String, String> keyRequestParameters,
MediaDrmCallback callback,
Looper playbackLooper,
- EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher,
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
if (mode == DefaultDrmSessionManager.MODE_QUERY
|| mode == DefaultDrmSessionManager.MODE_RELEASE) {
@@ -181,7 +180,7 @@
}
this.keyRequestParameters = keyRequestParameters;
this.callback = callback;
- this.eventDispatcher = eventDispatcher;
+ this.eventDispatchers = new CopyOnWriteMultiset<>();
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
state = STATE_OPENING;
responseHandler = new ResponseHandler(playbackLooper);
@@ -241,7 +240,7 @@
}
@Override
- public final @Nullable T getMediaCrypto() {
+ public final @Nullable ExoMediaCrypto getMediaCrypto() {
return mediaCrypto;
}
@@ -258,21 +257,32 @@
}
@Override
- public void acquire() {
+ public void acquire(@Nullable MediaSourceEventDispatcher eventDispatcher) {
Assertions.checkState(referenceCount >= 0);
+ if (eventDispatcher != null) {
+ eventDispatchers.add(eventDispatcher);
+ }
if (++referenceCount == 1) {
Assertions.checkState(state == STATE_OPENING);
- requestHandlerThread = new HandlerThread("DrmRequestHandler");
+ requestHandlerThread = new HandlerThread("ExoPlayer:DrmRequestHandler");
requestHandlerThread.start();
requestHandler = new RequestHandler(requestHandlerThread.getLooper());
if (openInternal(true)) {
doLicense(true);
}
+ } else {
+ // TODO: Add a parameter to onDrmSessionAcquired to indicate whether the session is being
+ // re-used or not.
+ if (eventDispatcher != null) {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionAcquired(),
+ DrmSessionEventListener.class);
+ }
}
}
@Override
- public void release() {
+ public void release(@Nullable MediaSourceEventDispatcher eventDispatcher) {
if (--referenceCount == 0) {
// Assigning null to various non-null variables for clean-up.
state = STATE_RELEASED;
@@ -288,10 +298,13 @@
if (sessionId != null) {
mediaDrm.closeSession(sessionId);
sessionId = null;
- eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionReleased);
}
releaseCallback.onSessionReleased(this);
}
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionReleased());
+ if (eventDispatcher != null) {
+ eventDispatchers.remove(eventDispatcher);
+ }
}
// Internal methods.
@@ -313,7 +326,7 @@
try {
sessionId = mediaDrm.openSession();
mediaCrypto = mediaDrm.createMediaCrypto(sessionId);
- eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionAcquired);
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionAcquired());
state = STATE_OPENED;
Assertions.checkNotNull(sessionId);
return true;
@@ -377,7 +390,7 @@
onError(new KeysExpiredException());
} else {
state = STATE_OPENED_WITH_KEYS;
- eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmKeysRestored);
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRestored());
}
}
break;
@@ -447,7 +460,7 @@
byte[] responseData = (byte[]) response;
if (mode == DefaultDrmSessionManager.MODE_RELEASE) {
mediaDrm.provideKeyResponse(Util.castNonNull(offlineLicenseKeySetId), responseData);
- eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmKeysRestored);
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRestored());
} else {
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, responseData);
if ((mode == DefaultDrmSessionManager.MODE_DOWNLOAD
@@ -458,7 +471,7 @@
offlineLicenseKeySetId = keySetId;
}
state = STATE_OPENED_WITH_KEYS;
- eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmKeysLoaded);
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded());
}
} catch (Exception e) {
onKeysError(e);
@@ -482,7 +495,7 @@
private void onError(final Exception e) {
lastException = new DrmSessionException(e);
- eventDispatcher.dispatch(listener -> listener.onDrmSessionManagerError(e));
+ dispatchEvent((listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionManagerError(e));
if (state != STATE_OPENED_WITH_KEYS) {
state = STATE_ERROR;
}
@@ -494,6 +507,13 @@
return state == STATE_OPENED || state == STATE_OPENED_WITH_KEYS;
}
+ private void dispatchEvent(
+ MediaSourceEventDispatcher.EventWithPeriodId<DrmSessionEventListener> event) {
+ for (MediaSourceEventDispatcher eventDispatcher : eventDispatchers.elementSet()) {
+ eventDispatcher.dispatch(event, DrmSessionEventListener.class);
+ }
+ }
+
// Internal classes.
@SuppressLint("HandlerLeak")
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionEventListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionEventListener.java
deleted file mode 100644
index 297f26b..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionEventListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.drm;
-
-import com.google.android.exoplayer2.Player;
-
-/** Listener of {@link DefaultDrmSessionManager} events. */
-public interface DefaultDrmSessionEventListener {
-
- /** Called each time a drm session is acquired. */
- default void onDrmSessionAcquired() {}
-
- /** Called each time keys are loaded. */
- default void onDrmKeysLoaded() {}
-
- /**
- * Called when a drm error occurs.
- *
- * <p>This method being called does not indicate that playback has failed, or that it will fail.
- * The player may be able to recover from the error and continue. Hence applications should
- * <em>not</em> implement this method to display a user visible error or initiate an application
- * level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement
- * such behavior). This method is called to provide the application with an opportunity to log the
- * error if it wishes to do so.
- *
- * @param error The corresponding exception.
- */
- default void onDrmSessionManagerError(Exception error) {}
-
- /** Called each time offline keys are restored. */
- default void onDrmKeysRestored() {}
-
- /** Called each time offline keys are removed. */
- default void onDrmKeysRemoved() {}
-
- /** Called each time a drm session is released. */
- default void onDrmSessionReleased() {}
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
index 8b40466..dbfde1c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
@@ -16,12 +16,12 @@
package com.google.android.exoplayer2.drm;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
@@ -29,8 +29,8 @@
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.EventDispatcher;
import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -43,8 +43,8 @@
import java.util.UUID;
/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */
-@TargetApi(18)
-public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T> {
+@RequiresApi(18)
+public class DefaultDrmSessionManager implements DrmSessionManager {
/**
* Builder for {@link DefaultDrmSessionManager} instances.
@@ -55,7 +55,7 @@
private final HashMap<String, String> keyRequestParameters;
private UUID uuid;
- private ExoMediaDrm.Provider<ExoMediaCrypto> exoMediaDrmProvider;
+ private ExoMediaDrm.Provider exoMediaDrmProvider;
private boolean multiSession;
private int[] useDrmSessionsForClearContentTrackTypes;
private boolean playClearSamplesWithoutKeys;
@@ -76,27 +76,29 @@
* DefaultLoadErrorHandlingPolicy}.
* </ul>
*/
- @SuppressWarnings("unchecked")
public Builder() {
keyRequestParameters = new HashMap<>();
uuid = C.WIDEVINE_UUID;
- exoMediaDrmProvider = (ExoMediaDrm.Provider) FrameworkMediaDrm.DEFAULT_PROVIDER;
+ exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
useDrmSessionsForClearContentTrackTypes = new int[0];
}
/**
* Sets the key request parameters to pass as the last argument to {@link
- * ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}.
+ * ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null if not parameters need to
+ * be passed.
*
* <p>Custom data for PlayReady should be set under {@link #PLAYREADY_CUSTOM_DATA_KEY}.
*
* @param keyRequestParameters A map with parameters.
* @return This builder.
*/
- public Builder setKeyRequestParameters(Map<String, String> keyRequestParameters) {
+ public Builder setKeyRequestParameters(@Nullable Map<String, String> keyRequestParameters) {
this.keyRequestParameters.clear();
- this.keyRequestParameters.putAll(Assertions.checkNotNull(keyRequestParameters));
+ if (keyRequestParameters != null) {
+ this.keyRequestParameters.putAll(keyRequestParameters);
+ }
return this;
}
@@ -107,7 +109,6 @@
* @param exoMediaDrmProvider The {@link ExoMediaDrm.Provider}.
* @return This builder.
*/
- @SuppressWarnings({"rawtypes", "unchecked"})
public Builder setUuidAndExoMediaDrmProvider(
UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider) {
this.uuid = Assertions.checkNotNull(uuid);
@@ -180,8 +181,8 @@
}
/** Builds a {@link DefaultDrmSessionManager} instance. */
- public DefaultDrmSessionManager<ExoMediaCrypto> build(MediaDrmCallback mediaDrmCallback) {
- return new DefaultDrmSessionManager<>(
+ public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) {
+ return new DefaultDrmSessionManager(
uuid,
exoMediaDrmProvider,
mediaDrmCallback,
@@ -235,23 +236,22 @@
private static final String TAG = "DefaultDrmSessionMgr";
private final UUID uuid;
- private final ExoMediaDrm.Provider<T> exoMediaDrmProvider;
+ private final ExoMediaDrm.Provider exoMediaDrmProvider;
private final MediaDrmCallback callback;
private final HashMap<String, String> keyRequestParameters;
- private final EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher;
private final boolean multiSession;
private final int[] useDrmSessionsForClearContentTrackTypes;
private final boolean playClearSamplesWithoutKeys;
private final ProvisioningManagerImpl provisioningManagerImpl;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
- private final List<DefaultDrmSession<T>> sessions;
- private final List<DefaultDrmSession<T>> provisioningSessions;
+ private final List<DefaultDrmSession> sessions;
+ private final List<DefaultDrmSession> provisioningSessions;
private int prepareCallsCount;
- @Nullable private ExoMediaDrm<T> exoMediaDrm;
- @Nullable private DefaultDrmSession<T> placeholderDrmSession;
- @Nullable private DefaultDrmSession<T> noMultiSessionDrmSession;
+ @Nullable private ExoMediaDrm exoMediaDrm;
+ @Nullable private DefaultDrmSession placeholderDrmSession;
+ @Nullable private DefaultDrmSession noMultiSessionDrmSession;
@Nullable private Looper playbackLooper;
private int mode;
@Nullable private byte[] offlineLicenseKeySetId;
@@ -270,7 +270,7 @@
@Deprecated
public DefaultDrmSessionManager(
UUID uuid,
- ExoMediaDrm<T> exoMediaDrm,
+ ExoMediaDrm exoMediaDrm,
MediaDrmCallback callback,
@Nullable HashMap<String, String> keyRequestParameters) {
this(
@@ -295,7 +295,7 @@
@Deprecated
public DefaultDrmSessionManager(
UUID uuid,
- ExoMediaDrm<T> exoMediaDrm,
+ ExoMediaDrm exoMediaDrm,
MediaDrmCallback callback,
@Nullable HashMap<String, String> keyRequestParameters,
boolean multiSession) {
@@ -323,14 +323,14 @@
@Deprecated
public DefaultDrmSessionManager(
UUID uuid,
- ExoMediaDrm<T> exoMediaDrm,
+ ExoMediaDrm exoMediaDrm,
MediaDrmCallback callback,
@Nullable HashMap<String, String> keyRequestParameters,
boolean multiSession,
int initialDrmRequestRetryCount) {
this(
uuid,
- new ExoMediaDrm.AppManagedProvider<>(exoMediaDrm),
+ new ExoMediaDrm.AppManagedProvider(exoMediaDrm),
callback,
keyRequestParameters == null ? new HashMap<>() : keyRequestParameters,
multiSession,
@@ -341,7 +341,7 @@
private DefaultDrmSessionManager(
UUID uuid,
- ExoMediaDrm.Provider<T> exoMediaDrmProvider,
+ ExoMediaDrm.Provider exoMediaDrmProvider,
MediaDrmCallback callback,
HashMap<String, String> keyRequestParameters,
boolean multiSession,
@@ -354,7 +354,6 @@
this.exoMediaDrmProvider = exoMediaDrmProvider;
this.callback = callback;
this.keyRequestParameters = keyRequestParameters;
- this.eventDispatcher = new EventDispatcher<>();
this.multiSession = multiSession;
this.useDrmSessionsForClearContentTrackTypes = useDrmSessionsForClearContentTrackTypes;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
@@ -366,28 +365,9 @@
}
/**
- * Adds a {@link DefaultDrmSessionEventListener} to listen to drm session events.
- *
- * @param handler A handler to use when delivering events to {@code eventListener}.
- * @param eventListener A listener of events.
- */
- public final void addListener(Handler handler, DefaultDrmSessionEventListener eventListener) {
- eventDispatcher.addListener(handler, eventListener);
- }
-
- /**
- * Removes a {@link DefaultDrmSessionEventListener} from the list of drm session event listeners.
- *
- * @param eventListener The listener to remove.
- */
- public final void removeListener(DefaultDrmSessionEventListener eventListener) {
- eventDispatcher.removeListener(eventListener);
- }
-
- /**
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
- * called before {@link #acquireSession(Looper, DrmInitData)} or {@link
- * #acquirePlaceholderSession} is called.
+ * called before {@link #acquireSession(Looper, MediaSourceEventDispatcher, DrmInitData)} or
+ * {@link #acquirePlaceholderSession} is called.
*
* <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
* required.
@@ -470,9 +450,9 @@
@Override
@Nullable
- public DrmSession<T> acquirePlaceholderSession(Looper playbackLooper, int trackType) {
+ public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
assertExpectedPlaybackLooper(playbackLooper);
- ExoMediaDrm<T> exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
+ ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
boolean avoidPlaceholderDrmSessions =
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
&& FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
@@ -484,18 +464,21 @@
}
maybeCreateMediaDrmHandler(playbackLooper);
if (placeholderDrmSession == null) {
- DefaultDrmSession<T> placeholderDrmSession =
+ DefaultDrmSession placeholderDrmSession =
createNewDefaultSession(
/* schemeDatas= */ Collections.emptyList(), /* isPlaceholderSession= */ true);
sessions.add(placeholderDrmSession);
this.placeholderDrmSession = placeholderDrmSession;
}
- placeholderDrmSession.acquire();
+ placeholderDrmSession.acquire(/* eventDispatcher= */ null);
return placeholderDrmSession;
}
@Override
- public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) {
+ public DrmSession acquireSession(
+ Looper playbackLooper,
+ @Nullable MediaSourceEventDispatcher eventDispatcher,
+ DrmInitData drmInitData) {
assertExpectedPlaybackLooper(playbackLooper);
maybeCreateMediaDrmHandler(playbackLooper);
@@ -504,18 +487,22 @@
schemeDatas = getSchemeDatas(drmInitData, uuid, false);
if (schemeDatas.isEmpty()) {
final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
- eventDispatcher.dispatch(listener -> listener.onDrmSessionManagerError(error));
- return new ErrorStateDrmSession<>(new DrmSessionException(error));
+ if (eventDispatcher != null) {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionManagerError(error),
+ DrmSessionEventListener.class);
+ }
+ return new ErrorStateDrmSession(new DrmSessionException(error));
}
}
- @Nullable DefaultDrmSession<T> session;
+ @Nullable DefaultDrmSession session;
if (!multiSession) {
session = noMultiSessionDrmSession;
} else {
// Only use an existing session if it has matching init data.
session = null;
- for (DefaultDrmSession<T> existingSession : sessions) {
+ for (DefaultDrmSession existingSession : sessions) {
if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) {
session = existingSession;
break;
@@ -531,13 +518,13 @@
}
sessions.add(session);
}
- session.acquire();
+ session.acquire(eventDispatcher);
return session;
}
@Override
@Nullable
- public Class<T> getExoMediaCryptoType(DrmInitData drmInitData) {
+ public Class<? extends ExoMediaCrypto> getExoMediaCryptoType(DrmInitData drmInitData) {
return canAcquireSession(drmInitData)
? Assertions.checkNotNull(exoMediaDrm).getExoMediaCryptoType()
: null;
@@ -556,12 +543,12 @@
}
}
- private DefaultDrmSession<T> createNewDefaultSession(
+ private DefaultDrmSession createNewDefaultSession(
@Nullable List<SchemeData> schemeDatas, boolean isPlaceholderSession) {
Assertions.checkNotNull(exoMediaDrm);
// Placeholder sessions should always play clear samples without keys.
boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession;
- return new DefaultDrmSession<>(
+ return new DefaultDrmSession(
uuid,
exoMediaDrm,
/* provisioningManager= */ provisioningManagerImpl,
@@ -574,11 +561,10 @@
keyRequestParameters,
callback,
Assertions.checkNotNull(playbackLooper),
- eventDispatcher,
loadErrorHandlingPolicy);
}
- private void onSessionReleased(DefaultDrmSession<T> drmSession) {
+ private void onSessionReleased(DefaultDrmSession drmSession) {
sessions.remove(drmSession);
if (placeholderDrmSession == drmSession) {
placeholderDrmSession = null;
@@ -634,7 +620,7 @@
// The event is not associated with any particular session.
return;
}
- for (DefaultDrmSession<T> session : sessions) {
+ for (DefaultDrmSession session : sessions) {
if (session.hasSessionId(sessionId)) {
session.onMediaDrmEvent(msg.what);
return;
@@ -643,9 +629,9 @@
}
}
- private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager<T> {
+ private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager {
@Override
- public void provisionRequired(DefaultDrmSession<T> session) {
+ public void provisionRequired(DefaultDrmSession session) {
if (provisioningSessions.contains(session)) {
// The session has already requested provisioning.
return;
@@ -659,7 +645,7 @@
@Override
public void onProvisionCompleted() {
- for (DefaultDrmSession<T> session : provisioningSessions) {
+ for (DefaultDrmSession session : provisioningSessions) {
session.onProvisionCompleted();
}
provisioningSessions.clear();
@@ -667,22 +653,18 @@
@Override
public void onProvisionError(Exception error) {
- for (DefaultDrmSession<T> session : provisioningSessions) {
+ for (DefaultDrmSession session : provisioningSessions) {
session.onProvisionError(error);
}
provisioningSessions.clear();
}
}
- private class MediaDrmEventListener implements OnEventListener<T> {
+ private class MediaDrmEventListener implements OnEventListener {
@Override
public void onEvent(
- ExoMediaDrm<? extends T> md,
- @Nullable byte[] sessionId,
- int event,
- int extra,
- @Nullable byte[] data) {
+ ExoMediaDrm md, @Nullable byte[] sessionId, int event, int extra, @Nullable byte[] data) {
Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget();
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java
new file mode 100644
index 0000000..3f2aae7
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.drm;
+
+import android.media.MediaDrm;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
+import java.io.IOException;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/** A DRM session. */
+public interface DrmSession {
+
+ /**
+ * Acquires {@code newSession} then releases {@code previousSession}.
+ *
+ * <p>Invokes {@code newSession's} {@link #acquire(MediaSourceEventDispatcher)} and {@code
+ * previousSession's} {@link #release(MediaSourceEventDispatcher)} in that order (passing {@code
+ * eventDispatcher = null}). Null arguments are ignored. Does nothing if {@code previousSession}
+ * and {@code newSession} are the same session.
+ */
+ static void replaceSession(
+ @Nullable DrmSession previousSession, @Nullable DrmSession newSession) {
+ if (previousSession == newSession) {
+ // Do nothing.
+ return;
+ }
+ if (newSession != null) {
+ newSession.acquire(/* eventDispatcher= */ null);
+ }
+ if (previousSession != null) {
+ previousSession.release(/* eventDispatcher= */ null);
+ }
+ }
+
+ /** Wraps the throwable which is the cause of the error state. */
+ class DrmSessionException extends IOException {
+
+ public DrmSessionException(Throwable cause) {
+ super(cause);
+ }
+
+ }
+
+ /**
+ * The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link
+ * #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
+ @interface State {}
+ /**
+ * The session has been released.
+ */
+ int STATE_RELEASED = 0;
+ /**
+ * The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
+ */
+ int STATE_ERROR = 1;
+ /**
+ * The session is being opened.
+ */
+ int STATE_OPENING = 2;
+ /** The session is open, but does not have keys required for decryption. */
+ int STATE_OPENED = 3;
+ /** The session is open and has keys required for decryption. */
+ int STATE_OPENED_WITH_KEYS = 4;
+
+ /**
+ * Returns the current state of the session, which is one of {@link #STATE_ERROR},
+ * {@link #STATE_RELEASED}, {@link #STATE_OPENING}, {@link #STATE_OPENED} and
+ * {@link #STATE_OPENED_WITH_KEYS}.
+ */
+ @State int getState();
+
+ /** Returns whether this session allows playback of clear samples prior to keys being loaded. */
+ default boolean playClearSamplesWithoutKeys() {
+ return false;
+ }
+
+ /**
+ * Returns the cause of the error state, or null if {@link #getState()} is not {@link
+ * #STATE_ERROR}.
+ */
+ @Nullable
+ DrmSessionException getError();
+
+ /**
+ * Returns an {@link ExoMediaCrypto} for the open session, or null if called before the session
+ * has been opened or after it's been released.
+ */
+ @Nullable
+ ExoMediaCrypto getMediaCrypto();
+
+ /**
+ * Returns a map describing the key status for the session, or null if called before the session
+ * has been opened or after it's been released.
+ *
+ * <p>Since DRM license policies vary by vendor, the specific status field names are determined by
+ * each DRM vendor. Refer to your DRM provider documentation for definitions of the field names
+ * for a particular DRM engine plugin.
+ *
+ * @return A map describing the key status for the session, or null if called before the session
+ * has been opened or after it's been released.
+ * @see MediaDrm#queryKeyStatus(byte[])
+ */
+ @Nullable
+ Map<String, String> queryKeyStatus();
+
+ /**
+ * Returns the key set id of the offline license loaded into this session, or null if there isn't
+ * one.
+ */
+ @Nullable
+ byte[] getOfflineLicenseKeySetId();
+
+ /**
+ * Increments the reference count. When the caller no longer needs to use the instance, it must
+ * call {@link #release(MediaSourceEventDispatcher)} to decrement the reference count.
+ *
+ * @param eventDispatcher The {@link MediaSourceEventDispatcher} used to route DRM-related events
+ * dispatched from this session, or null if no event handling is needed.
+ */
+ void acquire(@Nullable MediaSourceEventDispatcher eventDispatcher);
+
+ /**
+ * Decrements the reference count. If the reference count drops to 0 underlying resources are
+ * released, and the instance cannot be re-used.
+ *
+ * @param eventDispatcher The {@link MediaSourceEventDispatcher} to disconnect when the session is
+ * released (the same instance (possibly null) that was passed by the caller to {@link
+ * #acquire(MediaSourceEventDispatcher)}).
+ */
+ void release(@Nullable MediaSourceEventDispatcher eventDispatcher);
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java
new file mode 100644
index 0000000..dd306d9
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionEventListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.drm;
+
+import com.google.android.exoplayer2.Player;
+
+/** Listener of {@link DrmSessionManager} events. */
+public interface DrmSessionEventListener {
+
+ /** Called each time a drm session is acquired. */
+ default void onDrmSessionAcquired() {}
+
+ /** Called each time keys are loaded. */
+ default void onDrmKeysLoaded() {}
+
+ /**
+ * Called when a drm error occurs.
+ *
+ * <p>This method being called does not indicate that playback has failed, or that it will fail.
+ * The player may be able to recover from the error and continue. Hence applications should
+ * <em>not</em> implement this method to display a user visible error or initiate an application
+ * level retry ({@link Player.EventListener#onPlayerError} is the appropriate place to implement
+ * such behavior). This method is called to provide the application with an opportunity to log the
+ * error if it wishes to do so.
+ *
+ * @param error The corresponding exception.
+ */
+ default void onDrmSessionManagerError(Exception error) {}
+
+ /** Called each time offline keys are restored. */
+ default void onDrmKeysRestored() {}
+
+ /** Called each time offline keys are removed. */
+ default void onDrmKeysRemoved() {}
+
+ /** Called each time a drm session is released. */
+ default void onDrmSessionReleased() {}
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
index 146c5d7..0283470 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
@@ -19,21 +19,19 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
-/**
- * Manages a DRM session.
- */
-public interface DrmSessionManager<T extends ExoMediaCrypto> {
+/** Manages a DRM session. */
+public interface DrmSessionManager {
/** Returns {@link #DUMMY}. */
- @SuppressWarnings("unchecked")
- static <T extends ExoMediaCrypto> DrmSessionManager<T> getDummyDrmSessionManager() {
- return (DrmSessionManager<T>) DUMMY;
+ static DrmSessionManager getDummyDrmSessionManager() {
+ return DUMMY;
}
/** {@link DrmSessionManager} that supports no DRM schemes. */
- DrmSessionManager<ExoMediaCrypto> DUMMY =
- new DrmSessionManager<ExoMediaCrypto>() {
+ DrmSessionManager DUMMY =
+ new DrmSessionManager() {
@Override
public boolean canAcquireSession(DrmInitData drmInitData) {
@@ -41,9 +39,11 @@
}
@Override
- public DrmSession<ExoMediaCrypto> acquireSession(
- Looper playbackLooper, DrmInitData drmInitData) {
- return new ErrorStateDrmSession<>(
+ public DrmSession acquireSession(
+ Looper playbackLooper,
+ @Nullable MediaSourceEventDispatcher eventDispatcher,
+ DrmInitData drmInitData) {
+ return new ErrorStateDrmSession(
new DrmSession.DrmSessionException(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
}
@@ -83,7 +83,7 @@
/**
* Returns a {@link DrmSession} that does not execute key requests, with an incremented reference
* count. When the caller no longer needs to use the instance, it must call {@link
- * DrmSession#release()} to decrement the reference count.
+ * DrmSession#release(MediaSourceEventDispatcher)} to decrement the reference count.
*
* <p>Placeholder {@link DrmSession DrmSessions} may be used to configure secure decoders for
* playback of clear content periods. This can reduce the cost of transitioning between clear and
@@ -96,21 +96,26 @@
* placeholder sessions.
*/
@Nullable
- default DrmSession<T> acquirePlaceholderSession(Looper playbackLooper, int trackType) {
+ default DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
return null;
}
/**
* Returns a {@link DrmSession} for the specified {@link DrmInitData}, with an incremented
* reference count. When the caller no longer needs to use the instance, it must call {@link
- * DrmSession#release()} to decrement the reference count.
+ * DrmSession#release(MediaSourceEventDispatcher)} to decrement the reference count.
*
* @param playbackLooper The looper associated with the media playback thread.
+ * @param eventDispatcher The {@link MediaSourceEventDispatcher} used to distribute events, and
+ * passed on to {@link DrmSession#acquire(MediaSourceEventDispatcher)}.
* @param drmInitData DRM initialization data. All contained {@link SchemeData}s must contain
* non-null {@link SchemeData#data}.
* @return The DRM session.
*/
- DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData);
+ DrmSession acquireSession(
+ Looper playbackLooper,
+ @Nullable MediaSourceEventDispatcher eventDispatcher,
+ DrmInitData drmInitData);
/**
* Returns the {@link ExoMediaCrypto} type returned by sessions acquired using the given {@link
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java
index b619d94..d8311f6 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java
@@ -26,21 +26,25 @@
/** An {@link ExoMediaDrm} that does not support any protection schemes. */
@RequiresApi(18)
-public final class DummyExoMediaDrm<T extends ExoMediaCrypto> implements ExoMediaDrm<T> {
+public final class DummyExoMediaDrm implements ExoMediaDrm {
/** Returns a new instance. */
- @SuppressWarnings("unchecked")
- public static <T extends ExoMediaCrypto> DummyExoMediaDrm<T> getInstance() {
- return (DummyExoMediaDrm<T>) new DummyExoMediaDrm<>();
+ public static DummyExoMediaDrm getInstance() {
+ return new DummyExoMediaDrm();
}
@Override
- public void setOnEventListener(OnEventListener<? super T> listener) {
+ public void setOnEventListener(@Nullable OnEventListener listener) {
// Do nothing.
}
@Override
- public void setOnKeyStatusChangeListener(OnKeyStatusChangeListener<? super T> listener) {
+ public void setOnKeyStatusChangeListener(@Nullable OnKeyStatusChangeListener listener) {
+ // Do nothing.
+ }
+
+ @Override
+ public void setOnExpirationUpdateListener(@Nullable OnExpirationUpdateListener listener) {
// Do nothing.
}
@@ -64,8 +68,8 @@
throw new IllegalStateException();
}
- @Nullable
@Override
+ @Nullable
public byte[] provideKeyResponse(byte[] scope, byte[] response) {
// Should not be invoked. No session should exist.
throw new IllegalStateException();
@@ -132,14 +136,14 @@
}
@Override
- public T createMediaCrypto(byte[] sessionId) {
+ public ExoMediaCrypto createMediaCrypto(byte[] sessionId) {
// Should not be invoked. No session should exist.
throw new IllegalStateException();
}
@Override
@Nullable
- public Class<T> getExoMediaCryptoType() {
+ public Class<ExoMediaCrypto> getExoMediaCryptoType() {
// No ExoMediaCrypto type is supported.
return null;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java
index 0028e47..ff0a861 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java
@@ -17,10 +17,11 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import java.util.Map;
/** A {@link DrmSession} that's in a terminal error state. */
-public final class ErrorStateDrmSession<T extends ExoMediaCrypto> implements DrmSession<T> {
+public final class ErrorStateDrmSession implements DrmSession {
private final DrmSessionException error;
@@ -46,7 +47,7 @@
@Override
@Nullable
- public T getMediaCrypto() {
+ public ExoMediaCrypto getMediaCrypto() {
return null;
}
@@ -63,12 +64,12 @@
}
@Override
- public void acquire() {
+ public void acquire(@Nullable MediaSourceEventDispatcher eventDispatcher) {
// Do nothing.
}
@Override
- public void release() {
+ public void release(@Nullable MediaSourceEventDispatcher eventDispatcher) {
// Do nothing.
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java
index b6ee644..957945f 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java
@@ -42,17 +42,17 @@
* new instance does not normally need to call {@link #acquire()}, and must call {@link #release()}
* when the instance is no longer required.
*/
-public interface ExoMediaDrm<T extends ExoMediaCrypto> {
+public interface ExoMediaDrm {
/** {@link ExoMediaDrm} instances provider. */
- interface Provider<T extends ExoMediaCrypto> {
+ interface Provider {
/**
* Returns an {@link ExoMediaDrm} instance with an incremented reference count. When the caller
* no longer needs to use the instance, it must call {@link ExoMediaDrm#release()} to decrement
* the reference count.
*/
- ExoMediaDrm<T> acquireExoMediaDrm(UUID uuid);
+ ExoMediaDrm acquireExoMediaDrm(UUID uuid);
}
/**
@@ -62,17 +62,17 @@
* instance, and remains responsible for calling {@link ExoMediaDrm#release()} on the instance
* when it's no longer being used.
*/
- final class AppManagedProvider<T extends ExoMediaCrypto> implements Provider<T> {
+ final class AppManagedProvider implements Provider {
- private final ExoMediaDrm<T> exoMediaDrm;
+ private final ExoMediaDrm exoMediaDrm;
/** Creates an instance that provides the given {@link ExoMediaDrm}. */
- public AppManagedProvider(ExoMediaDrm<T> exoMediaDrm) {
+ public AppManagedProvider(ExoMediaDrm exoMediaDrm) {
this.exoMediaDrm = exoMediaDrm;
}
@Override
- public ExoMediaDrm<T> acquireExoMediaDrm(UUID uuid) {
+ public ExoMediaDrm acquireExoMediaDrm(UUID uuid) {
exoMediaDrm.acquire();
return exoMediaDrm;
}
@@ -108,10 +108,8 @@
@SuppressWarnings("InlinedApi")
int KEY_TYPE_RELEASE = MediaDrm.KEY_TYPE_RELEASE;
- /**
- * @see android.media.MediaDrm.OnEventListener
- */
- interface OnEventListener<T extends ExoMediaCrypto> {
+ /** @see android.media.MediaDrm.OnEventListener */
+ interface OnEventListener {
/**
* Called when an event occurs that requires the app to be notified
*
@@ -122,17 +120,15 @@
* @param data Optional byte array of data that may be associated with the event.
*/
void onEvent(
- ExoMediaDrm<? extends T> mediaDrm,
+ ExoMediaDrm mediaDrm,
@Nullable byte[] sessionId,
int event,
int extra,
@Nullable byte[] data);
}
- /**
- * @see android.media.MediaDrm.OnKeyStatusChangeListener
- */
- interface OnKeyStatusChangeListener<T extends ExoMediaCrypto> {
+ /** @see android.media.MediaDrm.OnKeyStatusChangeListener */
+ interface OnKeyStatusChangeListener {
/**
* Called when the keys in a session change status, such as when the license is renewed or
* expires.
@@ -143,12 +139,28 @@
* @param hasNewUsableKey Whether a new key became usable.
*/
void onKeyStatusChange(
- ExoMediaDrm<? extends T> mediaDrm,
+ ExoMediaDrm mediaDrm,
byte[] sessionId,
List<KeyStatus> exoKeyInformation,
boolean hasNewUsableKey);
}
+ /** @see android.media.MediaDrm.OnExpirationUpdateListener */
+ interface OnExpirationUpdateListener {
+
+ /**
+ * Called when a session expiration update occurs, to inform the app about the change in
+ * expiration time
+ *
+ * @param mediaDrm The {@link ExoMediaDrm} object on which the event occurred.
+ * @param sessionId The DRM session ID on which the event occurred
+ * @param expirationTimeMs The new expiration time for the keys in the session. The time is in
+ * milliseconds, relative to the Unix epoch. A time of 0 indicates that the keys never
+ * expire.
+ */
+ void onExpirationUpdate(ExoMediaDrm mediaDrm, byte[] sessionId, long expirationTimeMs);
+ }
+
/** @see android.media.MediaDrm.KeyStatus */
final class KeyStatus {
@@ -213,18 +225,42 @@
}
/**
+ * Sets the listener for DRM events.
+ *
+ * <p>This is an optional method, and some implementations may only support it on certain Android
+ * API levels.
+ *
+ * @param listener The listener to receive events, or {@code null} to stop receiving events.
+ * @throws UnsupportedOperationException if the implementation doesn't support this method.
* @see MediaDrm#setOnEventListener(MediaDrm.OnEventListener)
*/
- void setOnEventListener(OnEventListener<? super T> listener);
+ void setOnEventListener(@Nullable OnEventListener listener);
/**
+ * Sets the listener for key status change events.
+ *
+ * <p>This is an optional method, and some implementations may only support it on certain Android
+ * API levels.
+ *
+ * @param listener The listener to receive events, or {@code null} to stop receiving events.
+ * @throws UnsupportedOperationException if the implementation doesn't support this method.
* @see MediaDrm#setOnKeyStatusChangeListener(MediaDrm.OnKeyStatusChangeListener, Handler)
*/
- void setOnKeyStatusChangeListener(OnKeyStatusChangeListener<? super T> listener);
+ void setOnKeyStatusChangeListener(@Nullable OnKeyStatusChangeListener listener);
/**
- * @see MediaDrm#openSession()
+ * Sets the listener for session expiration events.
+ *
+ * <p>This is an optional method, and some implementations may only support it on certain Android
+ * API levels.
+ *
+ * @param listener The listener to receive events, or {@code null} to stop receiving events.
+ * @throws UnsupportedOperationException if the implementation doesn't support this method.
+ * @see MediaDrm#setOnExpirationUpdateListener(MediaDrm.OnExpirationUpdateListener, Handler)
*/
+ void setOnExpirationUpdateListener(@Nullable OnExpirationUpdateListener listener);
+
+ /** @see MediaDrm#openSession() */
byte[] openSession() throws MediaDrmException;
/**
@@ -331,12 +367,12 @@
* @return An object extends {@link ExoMediaCrypto}, using opaque crypto scheme specific data.
* @throws MediaCryptoException If the instance can't be created.
*/
- T createMediaCrypto(byte[] sessionId) throws MediaCryptoException;
+ ExoMediaCrypto createMediaCrypto(byte[] sessionId) throws MediaCryptoException;
/**
* Returns the {@link ExoMediaCrypto} type created by {@link #createMediaCrypto(byte[])}, or null
* if this instance cannot create any {@link ExoMediaCrypto} instances.
*/
@Nullable
- Class<T> getExoMediaCryptoType();
+ Class<? extends ExoMediaCrypto> getExoMediaCryptoType();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java
index 8ac92b0..2227738 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java
@@ -47,7 +47,7 @@
/** An {@link ExoMediaDrm} implementation that wraps the framework {@link MediaDrm}. */
@TargetApi(23)
@RequiresApi(18)
-public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto> {
+public final class FrameworkMediaDrm implements ExoMediaDrm {
private static final String TAG = "FrameworkMediaDrm";
@@ -56,13 +56,13 @@
* UUID. Returns a {@link DummyExoMediaDrm} if the protection scheme identified by the given UUID
* is not supported by the device.
*/
- public static final Provider<FrameworkMediaCrypto> DEFAULT_PROVIDER =
+ public static final Provider DEFAULT_PROVIDER =
uuid -> {
try {
return newInstance(uuid);
} catch (UnsupportedDrmException e) {
Log.e(TAG, "Failed to instantiate a FrameworkMediaDrm for uuid: " + uuid + ".");
- return new DummyExoMediaDrm<>();
+ return new DummyExoMediaDrm();
}
};
@@ -106,8 +106,7 @@
}
@Override
- public void setOnEventListener(
- final ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto> listener) {
+ public void setOnEventListener(@Nullable ExoMediaDrm.OnEventListener listener) {
mediaDrm.setOnEventListener(
listener == null
? null
@@ -115,9 +114,16 @@
listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data));
}
+ /**
+ * {@inheritDoc}
+ *
+ * @param listener The listener to receive events, or {@code null} to stop receiving events.
+ * @throws UnsupportedOperationException on API levels lower than 23.
+ */
@Override
+ @RequiresApi(23)
public void setOnKeyStatusChangeListener(
- final ExoMediaDrm.OnKeyStatusChangeListener<? super FrameworkMediaCrypto> listener) {
+ @Nullable ExoMediaDrm.OnKeyStatusChangeListener listener) {
if (Util.SDK_INT < 23) {
throw new UnsupportedOperationException();
}
@@ -133,7 +139,29 @@
listener.onKeyStatusChange(
FrameworkMediaDrm.this, sessionId, exoKeyInfo, hasNewUsableKey);
},
- null);
+ /* handler= */ null);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param listener The listener to receive events, or {@code null} to stop receiving events.
+ * @throws UnsupportedOperationException on API levels lower than 23.
+ */
+ @Override
+ @RequiresApi(23)
+ public void setOnExpirationUpdateListener(@Nullable OnExpirationUpdateListener listener) {
+ if (Util.SDK_INT < 23) {
+ throw new UnsupportedOperationException();
+ }
+
+ mediaDrm.setOnExpirationUpdateListener(
+ listener == null
+ ? null
+ : (mediaDrm, sessionId, expirationTimeMs) -> {
+ listener.onExpirationUpdate(FrameworkMediaDrm.this, sessionId, expirationTimeMs);
+ },
+ /* handler= */ null);
}
@Override
@@ -179,8 +207,8 @@
return new KeyRequest(requestData, licenseServerUrl);
}
- @Nullable
@Override
+ @Nullable
public byte[] provideKeyResponse(byte[] scope, byte[] response)
throws NotProvisionedException, DeniedByServerException {
if (C.CLEARKEY_UUID.equals(uuid)) {
@@ -341,14 +369,20 @@
C.PLAYREADY_UUID, addLaUrlAttributeIfMissing(schemeSpecificData));
}
- // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon
- // devices also required data to be extracted from the PSSH atom for PlayReady.
- if ((Util.SDK_INT < 21 && C.WIDEVINE_UUID.equals(uuid))
+ // Prior to API level 21, the Widevine CDM required scheme specific data to be extracted from
+ // the PSSH atom. We also extract the data on API levels 21 and 22 because these API levels
+ // don't handle V1 PSSH atoms, but do handle scheme specific data regardless of whether it's
+ // extracted from a V0 or a V1 PSSH atom. Hence extracting the data allows us to support content
+ // that only provides V1 PSSH atoms. API levels 23 and above understand V0 and V1 PSSH atoms,
+ // and so we do not extract the data.
+ // Some Amazon devices also require data to be extracted from the PSSH atom for PlayReady.
+ if ((Util.SDK_INT < 23 && C.WIDEVINE_UUID.equals(uuid))
|| (C.PLAYREADY_UUID.equals(uuid)
&& "Amazon".equals(Util.MANUFACTURER)
&& ("AFTB".equals(Util.MODEL) // Fire TV Gen 1
|| "AFTS".equals(Util.MODEL) // Fire TV Gen 2
- || "AFTM".equals(Util.MODEL)))) { // Fire TV Stick Gen 1
+ || "AFTM".equals(Util.MODEL) // Fire TV Stick Gen 1
+ || "AFTT".equals(Util.MODEL)))) { // Fire TV Stick Gen 2
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(initData, uuid);
if (psshData != null) {
// Extraction succeeded, so return the extracted data.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
index 19c32da..5e232c1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java
@@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.drm;
-import android.annotation.TargetApi;
-import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
@@ -34,10 +32,7 @@
import java.util.Map;
import java.util.UUID;
-/**
- * A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances.
- */
-@TargetApi(18)
+/** A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances. */
public final class HttpMediaDrmCallback implements MediaDrmCallback {
private static final int MAX_MANUAL_REDIRECTS = 5;
@@ -53,7 +48,7 @@
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
*/
public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
- this(defaultLicenseUrl, false, dataSourceFactory);
+ this(defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory);
}
/**
@@ -152,15 +147,12 @@
int manualRedirectCount = 0;
while (true) {
DataSpec dataSpec =
- new DataSpec(
- Uri.parse(url),
- DataSpec.HTTP_METHOD_POST,
- httpBody,
- /* absoluteStreamPosition= */ 0,
- /* position= */ 0,
- /* length= */ C.LENGTH_UNSET,
- /* key= */ null,
- DataSpec.FLAG_ALLOW_GZIP);
+ new DataSpec.Builder()
+ .setUri(url)
+ .setHttpMethod(DataSpec.HTTP_METHOD_POST)
+ .setHttpBody(httpBody)
+ .setFlags(DataSpec.FLAG_ALLOW_GZIP)
+ .build();
DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
try {
return Util.toByteArray(inputStream);
@@ -170,7 +162,7 @@
boolean manuallyRedirect =
(e.responseCode == 307 || e.responseCode == 308)
&& manualRedirectCount++ < MAX_MANUAL_REDIRECTS;
- String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null;
+ @Nullable String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null;
if (redirectUrl == null) {
throw e;
}
@@ -184,12 +176,11 @@
private static @Nullable String getRedirectUrl(InvalidResponseCodeException exception) {
Map<String, List<String>> headerFields = exception.headerFields;
if (headerFields != null) {
- List<String> locationHeaders = headerFields.get("Location");
+ @Nullable List<String> locationHeaders = headerFields.get("Location");
if (locationHeaders != null && !locationHeaders.isEmpty()) {
return locationHeaders.get(0);
}
}
return null;
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
index 93a7585..6092f39 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.drm;
-import android.annotation.TargetApi;
import android.media.MediaDrm;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -23,26 +22,24 @@
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.upstream.HttpDataSource;
-import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.util.Assertions;
-import java.util.Collections;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import java.util.Map;
import java.util.UUID;
/** Helper class to download, renew and release offline licenses. */
-@TargetApi(18)
@RequiresApi(18)
-public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
+public final class OfflineLicenseHelper {
private static final DrmInitData DUMMY_DRM_INIT_DATA = new DrmInitData();
private final ConditionVariable conditionVariable;
- private final DefaultDrmSessionManager<T> drmSessionManager;
+ private final DefaultDrmSessionManager drmSessionManager;
private final HandlerThread handlerThread;
+ private final MediaSourceEventDispatcher eventDispatcher;
/**
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
@@ -51,14 +48,19 @@
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
+ * @param eventDispatcher A {@link MediaSourceEventDispatcher} used to distribute DRM-related
+ * events.
* @return A new instance which uses Widevine CDM.
- * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
- * instantiated.
*/
- public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
- String defaultLicenseUrl, Factory httpDataSourceFactory)
- throws UnsupportedDrmException {
- return newWidevineInstance(defaultLicenseUrl, false, httpDataSourceFactory, null);
+ public static OfflineLicenseHelper newWidevineInstance(
+ String defaultLicenseUrl,
+ HttpDataSource.Factory httpDataSourceFactory,
+ MediaSourceEventDispatcher eventDispatcher) {
+ return newWidevineInstance(
+ defaultLicenseUrl,
+ /* forceDefaultLicenseUrl= */ false,
+ httpDataSourceFactory,
+ eventDispatcher);
}
/**
@@ -70,15 +72,21 @@
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
+ * @param eventDispatcher A {@link MediaSourceEventDispatcher} used to distribute DRM-related
+ * events.
* @return A new instance which uses Widevine CDM.
- * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
- * instantiated.
*/
- public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
- String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory)
- throws UnsupportedDrmException {
- return newWidevineInstance(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory,
- null);
+ public static OfflineLicenseHelper newWidevineInstance(
+ String defaultLicenseUrl,
+ boolean forceDefaultLicenseUrl,
+ HttpDataSource.Factory httpDataSourceFactory,
+ MediaSourceEventDispatcher eventDispatcher) {
+ return newWidevineInstance(
+ defaultLicenseUrl,
+ forceDefaultLicenseUrl,
+ httpDataSourceFactory,
+ /* optionalKeyRequestParameters= */ null,
+ eventDispatcher);
}
/**
@@ -91,45 +99,62 @@
* include their own license URL.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link MediaDrm#getKeyRequest}. May be null.
+ * @param eventDispatcher A {@link MediaSourceEventDispatcher} used to distribute DRM-related
+ * events.
* @return A new instance which uses Widevine CDM.
- * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
- * instantiated.
* @see DefaultDrmSessionManager.Builder
*/
- public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
+ public static OfflineLicenseHelper newWidevineInstance(
String defaultLicenseUrl,
boolean forceDefaultLicenseUrl,
- Factory httpDataSourceFactory,
- @Nullable Map<String, String> optionalKeyRequestParameters)
- throws UnsupportedDrmException {
- return new OfflineLicenseHelper<>(
- C.WIDEVINE_UUID,
- FrameworkMediaDrm.DEFAULT_PROVIDER,
- new HttpMediaDrmCallback(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory),
- optionalKeyRequestParameters);
+ HttpDataSource.Factory httpDataSourceFactory,
+ @Nullable Map<String, String> optionalKeyRequestParameters,
+ MediaSourceEventDispatcher eventDispatcher) {
+ return new OfflineLicenseHelper(
+ new DefaultDrmSessionManager.Builder()
+ .setKeyRequestParameters(optionalKeyRequestParameters)
+ .build(
+ new HttpMediaDrmCallback(
+ defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory)),
+ eventDispatcher);
+ }
+
+ /**
+ * @deprecated Use {@link #OfflineLicenseHelper(DefaultDrmSessionManager,
+ * MediaSourceEventDispatcher)} instead.
+ */
+ @Deprecated
+ public OfflineLicenseHelper(
+ UUID uuid,
+ ExoMediaDrm.Provider mediaDrmProvider,
+ MediaDrmCallback callback,
+ @Nullable Map<String, String> optionalKeyRequestParameters,
+ MediaSourceEventDispatcher eventDispatcher) {
+ this(
+ new DefaultDrmSessionManager.Builder()
+ .setUuidAndExoMediaDrmProvider(uuid, mediaDrmProvider)
+ .setKeyRequestParameters(optionalKeyRequestParameters)
+ .build(callback),
+ eventDispatcher);
}
/**
* Constructs an instance. Call {@link #release()} when the instance is no longer required.
*
- * @param uuid The UUID of the drm scheme.
- * @param mediaDrmProvider A {@link ExoMediaDrm.Provider}.
- * @param callback Performs key and provisioning requests.
- * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
- * to {@link MediaDrm#getKeyRequest}. May be null.
- * @see DefaultDrmSessionManager.Builder
+ * @param defaultDrmSessionManager The {@link DefaultDrmSessionManager} used to download licenses.
+ * @param eventDispatcher A {@link MediaSourceEventDispatcher} used to distribute DRM-related
+ * events.
*/
- @SuppressWarnings("unchecked")
public OfflineLicenseHelper(
- UUID uuid,
- ExoMediaDrm.Provider<T> mediaDrmProvider,
- MediaDrmCallback callback,
- @Nullable Map<String, String> optionalKeyRequestParameters) {
- handlerThread = new HandlerThread("OfflineLicenseHelper");
+ DefaultDrmSessionManager defaultDrmSessionManager,
+ MediaSourceEventDispatcher eventDispatcher) {
+ this.drmSessionManager = defaultDrmSessionManager;
+ this.eventDispatcher = eventDispatcher;
+ handlerThread = new HandlerThread("ExoPlayer:OfflineLicenseHelper");
handlerThread.start();
conditionVariable = new ConditionVariable();
- DefaultDrmSessionEventListener eventListener =
- new DefaultDrmSessionEventListener() {
+ DrmSessionEventListener eventListener =
+ new DrmSessionEventListener() {
@Override
public void onDrmKeysLoaded() {
conditionVariable.open();
@@ -150,16 +175,8 @@
conditionVariable.open();
}
};
- if (optionalKeyRequestParameters == null) {
- optionalKeyRequestParameters = Collections.emptyMap();
- }
- drmSessionManager =
- (DefaultDrmSessionManager<T>)
- new DefaultDrmSessionManager.Builder()
- .setUuidAndExoMediaDrmProvider(uuid, mediaDrmProvider)
- .setKeyRequestParameters(optionalKeyRequestParameters)
- .build(callback);
- drmSessionManager.addListener(new Handler(handlerThread.getLooper()), eventListener);
+ eventDispatcher.addEventListener(
+ new Handler(handlerThread.getLooper()), eventListener, DrmSessionEventListener.class);
}
/**
@@ -212,13 +229,13 @@
throws DrmSessionException {
Assertions.checkNotNull(offlineLicenseKeySetId);
drmSessionManager.prepare();
- DrmSession<T> drmSession =
+ DrmSession drmSession =
openBlockingKeyRequest(
DefaultDrmSessionManager.MODE_QUERY, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA);
DrmSessionException error = drmSession.getError();
Pair<Long, Long> licenseDurationRemainingSec =
WidevineUtil.getLicenseDurationRemainingSec(drmSession);
- drmSession.release();
+ drmSession.release(eventDispatcher);
drmSessionManager.release();
if (error != null) {
if (error.getCause() instanceof KeysExpiredException) {
@@ -240,11 +257,11 @@
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData)
throws DrmSessionException {
drmSessionManager.prepare();
- DrmSession<T> drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId,
- drmInitData);
+ DrmSession drmSession =
+ openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, drmInitData);
DrmSessionException error = drmSession.getError();
byte[] keySetId = drmSession.getOfflineLicenseKeySetId();
- drmSession.release();
+ drmSession.release(eventDispatcher);
drmSessionManager.release();
if (error != null) {
throw error;
@@ -252,12 +269,12 @@
return Assertions.checkNotNull(keySetId);
}
- private DrmSession<T> openBlockingKeyRequest(
+ private DrmSession openBlockingKeyRequest(
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData) {
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
conditionVariable.close();
- DrmSession<T> drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(),
- drmInitData);
+ DrmSession drmSession =
+ drmSessionManager.acquireSession(handlerThread.getLooper(), eventDispatcher, drmInitData);
// Block current thread until key loading is finished
conditionVariable.block();
return drmSession;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java
index 004f873..5f60ad6 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java
@@ -39,8 +39,7 @@
* @return A {@link Pair} consisting of the remaining license and playback durations in seconds,
* or null if called before the session has been opened or after it's been released.
*/
- public static @Nullable Pair<Long, Long> getLicenseDurationRemainingSec(
- DrmSession<?> drmSession) {
+ public static @Nullable Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession drmSession) {
Map<String, String> keyStatus = drmSession.queryKeyStatus();
if (keyStatus == null) {
return null;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
index 6fb49f7..040ef34 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
@@ -23,12 +23,13 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.util.Assertions;
/**
* A {@link MediaCodecAdapter} that operates the {@link MediaCodec} in asynchronous mode.
*
- * <p>The AsynchronousMediaCodecAdapter routes callbacks to the current Thread's {@link Looper}
+ * <p>The AsynchronousMediaCodecAdapter routes callbacks to the current thread's {@link Looper}
* obtained via {@link Looper#myLooper()}
*/
@RequiresApi(21)
@@ -71,8 +72,9 @@
@Override
public void queueSecureInputBuffer(
- int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags) {
- codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
+ codec.queueSecureInputBuffer(
+ index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags);
}
@Override
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
new file mode 100644
index 0000000..428d64d
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.mediacodec;
+
+import android.media.MediaCodec;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
+import com.google.android.exoplayer2.util.ConditionVariable;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/**
+ * A {@link MediaCodecInputBufferEnqueuer} that defers queueing operations on a background thread.
+ *
+ * <p>The implementation of this class assumes that its public methods will be called from the same
+ * thread.
+ */
+@RequiresApi(23)
+class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnqueuer {
+
+ private static final int MSG_QUEUE_INPUT_BUFFER = 0;
+ private static final int MSG_QUEUE_SECURE_INPUT_BUFFER = 1;
+ private static final int MSG_FLUSH = 2;
+
+ @GuardedBy("MESSAGE_PARAMS_INSTANCE_POOL")
+ private static final ArrayDeque<MessageParams> MESSAGE_PARAMS_INSTANCE_POOL = new ArrayDeque<>();
+
+ private static final Object QUEUE_SECURE_LOCK = new Object();
+
+ private final MediaCodec codec;
+ private final HandlerThread handlerThread;
+ private @MonotonicNonNull Handler handler;
+ private final AtomicReference<@NullableType RuntimeException> pendingRuntimeException;
+ private final ConditionVariable conditionVariable;
+ private final boolean needsSynchronizationWorkaround;
+ private boolean started;
+
+ /**
+ * Creates a new instance that submits input buffers on the specified {@link MediaCodec}.
+ *
+ * @param codec The {@link MediaCodec} to submit input buffers to.
+ * @param trackType The type of stream (used for debug logs).
+ */
+ public AsynchronousMediaCodecBufferEnqueuer(MediaCodec codec, int trackType) {
+ this(
+ codec,
+ new HandlerThread(createThreadLabel(trackType)),
+ /* conditionVariable= */ new ConditionVariable());
+ }
+
+ @VisibleForTesting
+ /* package */ AsynchronousMediaCodecBufferEnqueuer(
+ MediaCodec codec, HandlerThread handlerThread, ConditionVariable conditionVariable) {
+ this.codec = codec;
+ this.handlerThread = handlerThread;
+ this.conditionVariable = conditionVariable;
+ pendingRuntimeException = new AtomicReference<>();
+ needsSynchronizationWorkaround = needsSynchronizationWorkaround();
+ }
+
+ @Override
+ public void start() {
+ if (!started) {
+ handlerThread.start();
+ handler =
+ new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ doHandleMessage(msg);
+ }
+ };
+ started = true;
+ }
+ }
+
+ @Override
+ public void queueInputBuffer(
+ int index, int offset, int size, long presentationTimeUs, int flags) {
+ maybeThrowException();
+ MessageParams messageParams = getMessageParams();
+ messageParams.setQueueParams(index, offset, size, presentationTimeUs, flags);
+ Message message =
+ Util.castNonNull(handler).obtainMessage(MSG_QUEUE_INPUT_BUFFER, messageParams);
+ message.sendToTarget();
+ }
+
+ @Override
+ public void queueSecureInputBuffer(
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
+ maybeThrowException();
+ MessageParams messageParams = getMessageParams();
+ messageParams.setQueueParams(index, offset, /* size= */ 0, presentationTimeUs, flags);
+ copy(info, messageParams.cryptoInfo);
+ Message message =
+ Util.castNonNull(handler).obtainMessage(MSG_QUEUE_SECURE_INPUT_BUFFER, messageParams);
+ message.sendToTarget();
+ }
+
+ @Override
+ public void flush() {
+ if (started) {
+ try {
+ flushHandlerThread();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // The playback thread should not be interrupted. Raising this as an
+ // IllegalStateException.
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ if (started) {
+ flush();
+ handlerThread.quit();
+ }
+ started = false;
+ }
+
+ private void doHandleMessage(Message msg) {
+ MessageParams params = null;
+ switch (msg.what) {
+ case MSG_QUEUE_INPUT_BUFFER:
+ params = (MessageParams) msg.obj;
+ doQueueInputBuffer(
+ params.index, params.offset, params.size, params.presentationTimeUs, params.flags);
+ break;
+ case MSG_QUEUE_SECURE_INPUT_BUFFER:
+ params = (MessageParams) msg.obj;
+ doQueueSecureInputBuffer(
+ params.index,
+ params.offset,
+ params.cryptoInfo,
+ params.presentationTimeUs,
+ params.flags);
+ break;
+ case MSG_FLUSH:
+ conditionVariable.open();
+ break;
+ default:
+ setPendingRuntimeException(new IllegalStateException(String.valueOf(msg.what)));
+ }
+ if (params != null) {
+ recycleMessageParams(params);
+ }
+ }
+
+ private void maybeThrowException() {
+ RuntimeException exception = pendingRuntimeException.getAndSet(null);
+ if (exception != null) {
+ throw exception;
+ }
+ }
+
+ /**
+ * Empties all tasks enqueued on the {@link #handlerThread} via the {@link #handler}. This method
+ * blocks until the {@link #handlerThread} is idle.
+ */
+ private void flushHandlerThread() throws InterruptedException {
+ Handler handler = Util.castNonNull(this.handler);
+ handler.removeCallbacksAndMessages(null);
+ conditionVariable.close();
+ handler.obtainMessage(MSG_FLUSH).sendToTarget();
+ conditionVariable.block();
+ // Check if any exceptions happened during the last queueing action.
+ maybeThrowException();
+ }
+
+ // Called from the handler thread
+
+ @VisibleForTesting
+ /* package */ void setPendingRuntimeException(RuntimeException exception) {
+ pendingRuntimeException.set(exception);
+ }
+
+ private void doQueueInputBuffer(
+ int index, int offset, int size, long presentationTimeUs, int flag) {
+ try {
+ codec.queueInputBuffer(index, offset, size, presentationTimeUs, flag);
+ } catch (RuntimeException e) {
+ setPendingRuntimeException(e);
+ }
+ }
+
+ private void doQueueSecureInputBuffer(
+ int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags) {
+ try {
+ if (needsSynchronizationWorkaround) {
+ synchronized (QUEUE_SECURE_LOCK) {
+ codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ }
+ } else {
+ codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ }
+ } catch (RuntimeException e) {
+ setPendingRuntimeException(e);
+ }
+ }
+
+ @VisibleForTesting
+ /* package */ static int getInstancePoolSize() {
+ synchronized (MESSAGE_PARAMS_INSTANCE_POOL) {
+ return MESSAGE_PARAMS_INSTANCE_POOL.size();
+ }
+ }
+
+ private static MessageParams getMessageParams() {
+ synchronized (MESSAGE_PARAMS_INSTANCE_POOL) {
+ if (MESSAGE_PARAMS_INSTANCE_POOL.isEmpty()) {
+ return new MessageParams();
+ } else {
+ return MESSAGE_PARAMS_INSTANCE_POOL.removeFirst();
+ }
+ }
+ }
+
+ private static void recycleMessageParams(MessageParams params) {
+ synchronized (MESSAGE_PARAMS_INSTANCE_POOL) {
+ MESSAGE_PARAMS_INSTANCE_POOL.add(params);
+ }
+ }
+
+ /** Parameters for queue input buffer and queue secure input buffer tasks. */
+ private static class MessageParams {
+ public int index;
+ public int offset;
+ public int size;
+ public final MediaCodec.CryptoInfo cryptoInfo;
+ public long presentationTimeUs;
+ public int flags;
+
+ MessageParams() {
+ cryptoInfo = new MediaCodec.CryptoInfo();
+ }
+
+ /** Convenience method for setting the queueing parameters. */
+ public void setQueueParams(
+ int index, int offset, int size, long presentationTimeUs, int flags) {
+ this.index = index;
+ this.offset = offset;
+ this.size = size;
+ this.presentationTimeUs = presentationTimeUs;
+ this.flags = flags;
+ }
+ }
+
+ /**
+ * Returns whether this device needs the synchronization workaround when queueing secure input
+ * buffers (see [Internal: b/149908061]).
+ */
+ private static boolean needsSynchronizationWorkaround() {
+ String manufacturer = Util.toLowerInvariant(Util.MANUFACTURER);
+ return manufacturer.contains("samsung") || manufacturer.contains("motorola");
+ }
+
+ private static String createThreadLabel(int trackType) {
+ StringBuilder labelBuilder = new StringBuilder("ExoPlayer:MediaCodecBufferEnqueuer:");
+ if (trackType == C.TRACK_TYPE_AUDIO) {
+ labelBuilder.append("Audio");
+ } else if (trackType == C.TRACK_TYPE_VIDEO) {
+ labelBuilder.append("Video");
+ } else {
+ labelBuilder.append("Unknown(").append(trackType).append(")");
+ }
+ return labelBuilder.toString();
+ }
+
+ /** Performs a deep copy of {@code cryptoInfo} to {@code frameworkCryptoInfo}. */
+ private static void copy(
+ CryptoInfo cryptoInfo, android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) {
+ // Update frameworkCryptoInfo fields directly because CryptoInfo.set performs an unnecessary
+ // object allocation on Android N.
+ frameworkCryptoInfo.numSubSamples = cryptoInfo.numSubSamples;
+ frameworkCryptoInfo.numBytesOfClearData =
+ copy(cryptoInfo.numBytesOfClearData, frameworkCryptoInfo.numBytesOfClearData);
+ frameworkCryptoInfo.numBytesOfEncryptedData =
+ copy(cryptoInfo.numBytesOfEncryptedData, frameworkCryptoInfo.numBytesOfEncryptedData);
+ frameworkCryptoInfo.key = copy(cryptoInfo.key, frameworkCryptoInfo.key);
+ frameworkCryptoInfo.iv = copy(cryptoInfo.iv, frameworkCryptoInfo.iv);
+ frameworkCryptoInfo.mode = cryptoInfo.mode;
+ if (Util.SDK_INT >= 24) {
+ android.media.MediaCodec.CryptoInfo.Pattern pattern =
+ new android.media.MediaCodec.CryptoInfo.Pattern(
+ cryptoInfo.encryptedBlocks, cryptoInfo.clearBlocks);
+ frameworkCryptoInfo.setPattern(pattern);
+ }
+ }
+
+ /**
+ * Copies {@code src}, reusing {@code dst} if it's at least as long as {@code src}.
+ *
+ * @param src The source array.
+ * @param dst The destination array, which will be reused if it's at least as long as {@code src}.
+ * @return The copy, which may be {@code dst} if it was reused.
+ */
+ private static int[] copy(int[] src, int[] dst) {
+ if (src == null) {
+ return dst;
+ }
+
+ if (dst == null || dst.length < src.length) {
+ return Arrays.copyOf(src, src.length);
+ } else {
+ System.arraycopy(src, 0, dst, 0, src.length);
+ return dst;
+ }
+ }
+
+ /**
+ * Copies {@code src}, reusing {@code dst} if it's at least as long as {@code src}.
+ *
+ * @param src The source array.
+ * @param dst The destination array, which will be reused if it's at least as long as {@code src}.
+ * @return The copy, which may be {@code dst} if it was reused.
+ */
+ private static byte[] copy(byte[] src, byte[] dst) {
+ if (src == null) {
+ return dst;
+ }
+
+ if (dst == null || dst.length < src.length) {
+ return Arrays.copyOf(src, src.length);
+ } else {
+ System.arraycopy(src, 0, dst, 0, src.length);
+ return dst;
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java
index d231a24..88e3f56 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java
@@ -21,23 +21,30 @@
import android.os.Handler;
import android.os.HandlerThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in asynchronous mode
- * and routes {@link MediaCodec.Callback} callbacks on a dedicated Thread that is managed
+ * and routes {@link MediaCodec.Callback} callbacks on a dedicated thread that is managed
* internally.
+ *
+ * <p>This adapter supports queueing input buffers asynchronously.
*/
@RequiresApi(23)
/* package */ final class DedicatedThreadAsyncMediaCodecAdapter extends MediaCodec.Callback
implements MediaCodecAdapter {
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_CREATED, STATE_STARTED, STATE_SHUT_DOWN})
private @interface State {}
@@ -52,29 +59,58 @@
private long pendingFlushCount;
private @State int state;
private Runnable codecStartRunnable;
+ private final MediaCodecInputBufferEnqueuer bufferEnqueuer;
@Nullable private IllegalStateException internalException;
/**
+ * Creates an instance that wraps the specified {@link MediaCodec}. Instances created with this
+ * constructor will queue input buffers to the {@link MediaCodec} synchronously.
+ *
+ * @param codec The {@link MediaCodec} to wrap.
+ * @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
+ * labelling the internal thread accordingly.
+ */
+ /* package */ DedicatedThreadAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
+ this(
+ codec,
+ /* enableAsynchronousQueueing= */ false,
+ trackType,
+ new HandlerThread(createThreadLabel(trackType)));
+ }
+
+ /**
* Creates an instance that wraps the specified {@link MediaCodec}.
*
* @param codec The {@link MediaCodec} to wrap.
+ * @param enableAsynchronousQueueing Whether input buffers will be queued asynchronously.
* @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
- * labelling the internal Thread accordingly.
- * @throws IllegalArgumentException If {@code trackType} is not one of {@link C#TRACK_TYPE_AUDIO}
- * or {@link C#TRACK_TYPE_VIDEO}.
+ * labelling the internal thread accordingly.
*/
- /* package */ DedicatedThreadAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
- this(codec, new HandlerThread(createThreadLabel(trackType)));
+ /* package */ DedicatedThreadAsyncMediaCodecAdapter(
+ MediaCodec codec, boolean enableAsynchronousQueueing, int trackType) {
+ this(
+ codec,
+ enableAsynchronousQueueing,
+ trackType,
+ new HandlerThread(createThreadLabel(trackType)));
}
@VisibleForTesting
/* package */ DedicatedThreadAsyncMediaCodecAdapter(
- MediaCodec codec, HandlerThread handlerThread) {
+ MediaCodec codec,
+ boolean enableAsynchronousQueueing,
+ int trackType,
+ HandlerThread handlerThread) {
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
this.codec = codec;
this.handlerThread = handlerThread;
state = STATE_CREATED;
codecStartRunnable = codec::start;
+ if (enableAsynchronousQueueing) {
+ bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType);
+ } else {
+ bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(this.codec);
+ }
}
@Override
@@ -82,6 +118,7 @@
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler);
+ bufferEnqueuer.start();
codecStartRunnable.run();
state = STATE_STARTED;
}
@@ -91,15 +128,15 @@
int index, int offset, int size, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it does not interact with the
// mediaCodecAsyncCallback.
- codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ bufferEnqueuer.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
}
@Override
public void queueSecureInputBuffer(
- int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags) {
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it does not interact with the
// mediaCodecAsyncCallback.
- codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
}
@Override
@@ -129,6 +166,7 @@
@Override
public synchronized void flush() {
+ bufferEnqueuer.flush();
codec.flush();
++pendingFlushCount;
Util.castNonNull(handler).post(this::onFlushCompleted);
@@ -137,33 +175,31 @@
@Override
public synchronized void shutdown() {
if (state == STATE_STARTED) {
+ bufferEnqueuer.shutdown();
handlerThread.quit();
mediaCodecAsyncCallback.flush();
}
-
state = STATE_SHUT_DOWN;
}
@Override
- public synchronized void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
+ public synchronized void onInputBufferAvailable(MediaCodec codec, int index) {
mediaCodecAsyncCallback.onInputBufferAvailable(codec, index);
}
@Override
public synchronized void onOutputBufferAvailable(
- @NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
+ MediaCodec codec, int index, MediaCodec.BufferInfo info) {
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, index, info);
}
@Override
- public synchronized void onError(
- @NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
+ public synchronized void onError(MediaCodec codec, MediaCodec.CodecException e) {
mediaCodecAsyncCallback.onError(codec, e);
}
@Override
- public synchronized void onOutputFormatChanged(
- @NonNull MediaCodec codec, @NonNull MediaFormat format) {
+ public synchronized void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
}
@@ -221,7 +257,7 @@
}
private static String createThreadLabel(int trackType) {
- StringBuilder labelBuilder = new StringBuilder("MediaCodecAsyncAdapter:");
+ StringBuilder labelBuilder = new StringBuilder("ExoPlayer:MediaCodecAsyncAdapter:");
if (trackType == C.TRACK_TYPE_AUDIO) {
labelBuilder.append("Audio");
} else if (trackType == C.TRACK_TYPE_VIDEO) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
index ce1ba41..1be850c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
@@ -18,6 +18,7 @@
import android.media.MediaCodec;
import android.media.MediaFormat;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
/**
* Abstracts {@link MediaCodec} operations.
@@ -81,10 +82,14 @@
* <p>The {@code index} must be an input buffer index that has been obtained from a previous call
* to {@link #dequeueInputBufferIndex()}.
*
+ * <p>Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference
+ * that {@code info} is of type {@link CryptoInfo} and not {@link
+ * android.media.MediaCodec.CryptoInfo}.
+ *
* @see MediaCodec#queueSecureInputBuffer
*/
void queueSecureInputBuffer(
- int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags);
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
/**
* Flushes the {@code MediaCodecAdapter}.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAsyncCallback.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAsyncCallback.java
index dc4bcbb..5be6031 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAsyncCallback.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAsyncCallback.java
@@ -17,7 +17,6 @@
import android.media.MediaCodec;
import android.media.MediaFormat;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
@@ -120,25 +119,24 @@
}
@Override
- public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int i) {
+ public void onInputBufferAvailable(MediaCodec mediaCodec, int i) {
availableInputBuffers.add(i);
}
@Override
public void onOutputBufferAvailable(
- @NonNull MediaCodec mediaCodec, int i, @NonNull MediaCodec.BufferInfo bufferInfo) {
+ MediaCodec mediaCodec, int i, MediaCodec.BufferInfo bufferInfo) {
availableOutputBuffers.add(i);
bufferInfos.add(bufferInfo);
}
@Override
- public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
+ public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
onMediaCodecError(e);
}
@Override
- public void onOutputFormatChanged(
- @NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
+ public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
formats.add(mediaFormat);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecDecoderException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecDecoderException.java
new file mode 100644
index 0000000..524f856
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecDecoderException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.mediacodec;
+
+import android.media.MediaCodec;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import com.google.android.exoplayer2.decoder.DecoderException;
+import com.google.android.exoplayer2.util.Util;
+
+/** Thrown when a failure occurs in a {@link MediaCodec} decoder. */
+public class MediaCodecDecoderException extends DecoderException {
+
+ /** The {@link MediaCodecInfo} of the decoder that failed. Null if unknown. */
+ @Nullable public final MediaCodecInfo codecInfo;
+
+ /** An optional developer-readable diagnostic information string. May be null. */
+ @Nullable public final String diagnosticInfo;
+
+ public MediaCodecDecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) {
+ super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause);
+ this.codecInfo = codecInfo;
+ diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
+ }
+
+ @RequiresApi(21)
+ @Nullable
+ private static String getDiagnosticInfoV21(Throwable cause) {
+ if (cause instanceof MediaCodec.CodecException) {
+ return ((MediaCodec.CodecException) cause).getDiagnosticInfo();
+ }
+ return null;
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
index 64517fe..4094f4b 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.mediacodec;
-import android.annotation.TargetApi;
import android.graphics.Point;
import android.media.MediaCodec;
import android.media.MediaCodecInfo.AudioCapabilities;
@@ -24,6 +23,7 @@
import android.media.MediaCodecInfo.VideoCapabilities;
import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
@@ -50,15 +50,14 @@
*/
public final String name;
- /** The MIME type handled by the codec, or {@code null} if this is a passthrough codec. */
- @Nullable public final String mimeType;
+ /** The MIME type handled by the codec. */
+ public final String mimeType;
/**
- * The MIME type that the codec uses for media of type {@link #mimeType}, or {@code null} if this
- * is a passthrough codec. Equal to {@link #mimeType} unless the codec is known to use a
- * non-standard MIME type alias.
+ * The MIME type that the codec uses for media of type {@link #mimeType}. Equal to {@link
+ * #mimeType} unless the codec is known to use a non-standard MIME type alias.
*/
- @Nullable public final String codecMimeType;
+ public final String codecMimeType;
/**
* The capabilities of the decoder, like the profiles/levels it supports, or {@code null} if not
@@ -90,9 +89,6 @@
*/
public final boolean secure;
- /** Whether this instance describes a passthrough codec. */
- public final boolean passthrough;
-
/**
* Whether the codec is hardware accelerated.
*
@@ -123,26 +119,6 @@
private final boolean isVideo;
/**
- * Creates an instance representing an audio passthrough decoder.
- *
- * @param name The name of the {@link MediaCodec}.
- * @return The created instance.
- */
- public static MediaCodecInfo newPassthroughInstance(String name) {
- return new MediaCodecInfo(
- name,
- /* mimeType= */ null,
- /* codecMimeType= */ null,
- /* capabilities= */ null,
- /* passthrough= */ true,
- /* hardwareAccelerated= */ false,
- /* softwareOnly= */ true,
- /* vendor= */ false,
- /* forceDisableAdaptive= */ false,
- /* forceSecure= */ false);
- }
-
- /**
* Creates an instance.
*
* @param name The name of the {@link MediaCodec}.
@@ -173,7 +149,6 @@
mimeType,
codecMimeType,
capabilities,
- /* passthrough= */ false,
hardwareAccelerated,
softwareOnly,
vendor,
@@ -183,10 +158,9 @@
private MediaCodecInfo(
String name,
- @Nullable String mimeType,
- @Nullable String codecMimeType,
+ String mimeType,
+ String codecMimeType,
@Nullable CodecCapabilities capabilities,
- boolean passthrough,
boolean hardwareAccelerated,
boolean softwareOnly,
boolean vendor,
@@ -196,7 +170,6 @@
this.mimeType = mimeType;
this.codecMimeType = codecMimeType;
this.capabilities = capabilities;
- this.passthrough = passthrough;
this.hardwareAccelerated = hardwareAccelerated;
this.softwareOnly = softwareOnly;
this.vendor = vendor;
@@ -229,9 +202,10 @@
* @see CodecCapabilities#getMaxSupportedInstances()
*/
public int getMaxSupportedInstances() {
- return (Util.SDK_INT < 23 || capabilities == null)
- ? MAX_SUPPORTED_INSTANCES_UNKNOWN
- : getMaxSupportedInstancesV23(capabilities);
+ if (Util.SDK_INT < 23 || capabilities == null) {
+ return MAX_SUPPORTED_INSTANCES_UNKNOWN;
+ }
+ return getMaxSupportedInstancesV23(capabilities);
}
/**
@@ -393,7 +367,7 @@
* Format#NO_VALUE} or any value less than or equal to 0.
* @return Whether the decoder supports video with the given width, height and frame rate.
*/
- @TargetApi(21)
+ @RequiresApi(21)
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
if (capabilities == null) {
logNoSupport("sizeAndRate.caps");
@@ -419,8 +393,8 @@
/**
* Returns the smallest video size greater than or equal to a specified size that also satisfies
* the {@link MediaCodec}'s width and height alignment requirements.
- * <p>
- * Must not be called if the device SDK version is less than 21.
+ *
+ * <p>Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
@@ -428,7 +402,7 @@
* the {@link MediaCodec}'s width and height alignment requirements, or null if not a video
* codec.
*/
- @TargetApi(21)
+ @RequiresApi(21)
public Point alignVideoSizeV21(int width, int height) {
if (capabilities == null) {
return null;
@@ -442,13 +416,13 @@
/**
* Whether the decoder supports audio with a given sample rate.
- * <p>
- * Must not be called if the device SDK version is less than 21.
+ *
+ * <p>Must not be called if the device SDK version is less than 21.
*
* @param sampleRate The sample rate in Hz.
* @return Whether the decoder supports audio with the given sample rate.
*/
- @TargetApi(21)
+ @RequiresApi(21)
public boolean isAudioSampleRateSupportedV21(int sampleRate) {
if (capabilities == null) {
logNoSupport("sampleRate.caps");
@@ -468,13 +442,13 @@
/**
* Whether the decoder supports audio with a given channel count.
- * <p>
- * Must not be called if the device SDK version is less than 21.
+ *
+ * <p>Must not be called if the device SDK version is less than 21.
*
* @param channelCount The channel count.
* @return Whether the decoder supports audio with the given channel count.
*/
- @TargetApi(21)
+ @RequiresApi(21)
public boolean isAudioChannelCountSupportedV21(int channelCount) {
if (capabilities == null) {
logNoSupport("channelCount.caps");
@@ -542,7 +516,7 @@
return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities);
}
- @TargetApi(19)
+ @RequiresApi(19)
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
@@ -551,7 +525,7 @@
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
}
- @TargetApi(21)
+ @RequiresApi(21)
private static boolean isTunnelingV21(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback);
}
@@ -560,14 +534,14 @@
return Util.SDK_INT >= 21 && isSecureV21(capabilities);
}
- @TargetApi(21)
+ @RequiresApi(21)
private static boolean isSecureV21(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
}
- @TargetApi(21)
- private static boolean areSizeAndRateSupportedV21(VideoCapabilities capabilities, int width,
- int height, double frameRate) {
+ @RequiresApi(21)
+ private static boolean areSizeAndRateSupportedV21(
+ VideoCapabilities capabilities, int width, int height, double frameRate) {
// Don't ever fail due to alignment. See: https://github.com/google/ExoPlayer/issues/6551.
Point alignedSize = alignVideoSizeV21(capabilities, width, height);
width = alignedSize.x;
@@ -584,7 +558,7 @@
}
}
- @TargetApi(21)
+ @RequiresApi(21)
private static Point alignVideoSizeV21(VideoCapabilities capabilities, int width, int height) {
int widthAlignment = capabilities.getWidthAlignment();
int heightAlignment = capabilities.getHeightAlignment();
@@ -593,7 +567,7 @@
Util.ceilDivide(height, heightAlignment) * heightAlignment);
}
- @TargetApi(23)
+ @RequiresApi(23)
private static int getMaxSupportedInstancesV23(CodecCapabilities capabilities) {
return capabilities.getMaxSupportedInstances();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java
new file mode 100644
index 0000000..34a1ccc
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.mediacodec;
+
+import android.media.MediaCodec;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
+
+/** Abstracts operations to enqueue input buffer on a {@link android.media.MediaCodec}. */
+interface MediaCodecInputBufferEnqueuer {
+
+ /**
+ * Starts this instance.
+ *
+ * <p>Call this method after creating an instance.
+ */
+ void start();
+
+ /**
+ * Submits an input buffer for decoding.
+ *
+ * @see android.media.MediaCodec#queueInputBuffer
+ */
+ void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
+
+ /**
+ * Submits an input buffer that potentially contains encrypted data for decoding.
+ *
+ * <p>Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference
+ * that {@code info} is of type {@link CryptoInfo} and not {@link
+ * android.media.MediaCodec.CryptoInfo}.
+ *
+ * @see android.media.MediaCodec#queueSecureInputBuffer
+ */
+ void queueSecureInputBuffer(
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
+
+ /** Flushes the instance. */
+ void flush();
+
+ /** Shut down the instance. Make sure to call this method to release its internal resources. */
+ void shutdown();
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
index ecb6bbf..bb772a7 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
@@ -28,19 +28,22 @@
import androidx.annotation.CheckResult;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.NalUnitUtil;
@@ -81,7 +84,9 @@
OPERATION_MODE_SYNCHRONOUS,
OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD,
- OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
+ OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK,
+ OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING,
+ OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING
})
public @interface MediaCodecOperationMode {}
@@ -89,19 +94,30 @@
public static final int OPERATION_MODE_SYNCHRONOUS = 0;
/**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
- * callbacks to the playback Thread.
+ * callbacks to the playback thread.
*/
public static final int OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD = 1;
/**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
- * callbacks to a dedicated Thread.
+ * callbacks to a dedicated thread.
*/
public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD = 2;
/**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
- * callbacks to a dedicated Thread. Uses granular locking for input and output buffers.
+ * callbacks to a dedicated thread. Uses granular locking for input and output buffers.
*/
public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK = 3;
+ /**
+ * Same as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}, and offloads queueing to another
+ * thread.
+ */
+ public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING = 4;
+ /**
+ * Same as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}, and offloads queueing
+ * to another thread.
+ */
+ public static final int
+ OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING = 5;
/** Thrown when a failure occurs instantiating a decoder. */
public static class DecoderInitializationException extends Exception {
@@ -136,8 +152,8 @@
*/
@Nullable public final DecoderInitializationException fallbackDecoderInitializationException;
- public DecoderInitializationException(Format format, Throwable cause,
- boolean secureDecoderRequired, int errorCode) {
+ public DecoderInitializationException(
+ Format format, @Nullable Throwable cause, boolean secureDecoderRequired, int errorCode) {
this(
"Decoder init failed: [" + errorCode + "], " + format,
cause,
@@ -150,7 +166,7 @@
public DecoderInitializationException(
Format format,
- Throwable cause,
+ @Nullable Throwable cause,
boolean secureDecoderRequired,
MediaCodecInfo mediaCodecInfo) {
this(
@@ -165,7 +181,7 @@
private DecoderInitializationException(
String message,
- Throwable cause,
+ @Nullable Throwable cause,
String mimeType,
boolean secureDecoderRequired,
@Nullable MediaCodecInfo mediaCodecInfo,
@@ -192,8 +208,9 @@
fallbackException);
}
- @TargetApi(21)
- private static String getDiagnosticInfoV21(Throwable cause) {
+ @RequiresApi(21)
+ @Nullable
+ private static String getDiagnosticInfoV21(@Nullable Throwable cause) {
if (cause instanceof CodecException) {
return ((CodecException) cause).getDiagnosticInfo();
}
@@ -208,30 +225,6 @@
}
}
- /** Thrown when a failure occurs in the decoder. */
- public static class DecoderException extends Exception {
-
- /** The {@link MediaCodecInfo} of the decoder that failed. Null if unknown. */
- @Nullable public final MediaCodecInfo codecInfo;
-
- /** An optional developer-readable diagnostic information string. May be null. */
- @Nullable public final String diagnosticInfo;
-
- public DecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) {
- super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause);
- this.codecInfo = codecInfo;
- diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
- }
-
- @TargetApi(21)
- private static String getDiagnosticInfoV21(Throwable cause) {
- if (cause instanceof CodecException) {
- return ((CodecException) cause).getDiagnosticInfo();
- }
- return null;
- }
- }
-
/** Indicates no codec operating rate should be set. */
protected static final float CODEC_OPERATING_RATE_UNSET = -1;
@@ -247,6 +240,10 @@
*/
private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000;
+ // Generally there is zero or one pending output stream offset. We track more offsets to allow for
+ // pending output streams that have fewer frames than the codec latency.
+ private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10;
+
/**
* The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format,
* Format)}.
@@ -360,8 +357,6 @@
private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32;
private final MediaCodecSelector mediaCodecSelector;
- @Nullable private final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
- private final boolean playClearSamplesWithoutKeys;
private final boolean enableDecoderFallback;
private final float assumedMinimumCodecOperatingRate;
private final DecoderInputBuffer buffer;
@@ -369,11 +364,13 @@
private final TimedValueQueue<Format> formatQueue;
private final ArrayList<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo;
+ private final long[] pendingOutputStreamOffsetsUs;
+ private final long[] pendingOutputStreamSwitchTimesUs;
@Nullable private Format inputFormat;
private Format outputFormat;
- @Nullable private DrmSession<FrameworkMediaCrypto> codecDrmSession;
- @Nullable private DrmSession<FrameworkMediaCrypto> sourceDrmSession;
+ @Nullable private DrmSession codecDrmSession;
+ @Nullable private DrmSession sourceDrmSession;
@Nullable private MediaCrypto mediaCrypto;
private boolean mediaCryptoRequiresSecureDecoder;
private long renderTimeLimitMs;
@@ -389,6 +386,7 @@
private boolean codecNeedsReconfigureWorkaround;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
+ private boolean codecNeedsSosFlushWorkaround;
private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
@@ -409,6 +407,7 @@
@DrainAction private int codecDrainAction;
private boolean codecReceivedBuffers;
private boolean codecReceivedEos;
+ private boolean codecHasOutputMediaFormat;
private long largestQueuedPresentationTimeUs;
private long lastBufferInStreamPresentationTimeUs;
private boolean inputStreamEnded;
@@ -419,18 +418,13 @@
private boolean pendingOutputEndOfStream;
@MediaCodecOperationMode private int mediaCodecOperationMode;
protected DecoderCounters decoderCounters;
+ private long outputStreamOffsetUs;
+ private int pendingOutputStreamOffsetCount;
/**
* @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*}
* constants defined in {@link C}.
* @param mediaCodecSelector A decoder selector.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
* initialization fails. This may result in using a decoder that is less efficient or slower
* than the primary decoder.
@@ -441,14 +435,10 @@
public MediaCodecRenderer(
int trackType,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
boolean enableDecoderFallback,
float assumedMinimumCodecOperatingRate) {
super(trackType);
this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector);
- this.drmSessionManager = drmSessionManager;
- this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
this.enableDecoderFallback = enableDecoderFallback;
this.assumedMinimumCodecOperatingRate = assumedMinimumCodecOperatingRate;
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
@@ -459,6 +449,9 @@
rendererOperatingRate = 1f;
renderTimeLimitMs = C.TIME_UNSET;
mediaCodecOperationMode = OPERATION_MODE_SYNCHRONOUS;
+ pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
+ pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
+ outputStreamOffsetUs = C.TIME_UNSET;
resetCodecStateForRelease();
}
@@ -484,21 +477,27 @@
*
* @param mode The mode of the MediaCodec. The supported modes are:
* <ul>
- * <li>{@link MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}: The {@link MediaCodec} will
- * operate in synchronous mode.
- * <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link
- * MediaCodec} will operate in asynchronous mode and {@link MediaCodec.Callback}
- * callbacks will be routed to the Playback Thread. This mode requires API level ≥
- * 21; if the API level is ≤ 20, the operation mode will be set to {@link
+ * <li>{@link #OPERATION_MODE_SYNCHRONOUS}: The {@link MediaCodec} will operate in
+ * synchronous mode.
+ * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link MediaCodec} will
+ * operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
+ * to the playback thread. This mode requires API level ≥ 21; if the API level is
+ * ≤ 20, the operation mode will be set to {@link
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
- * <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}: The {@link
- * MediaCodec} will operate in asynchronous mode and {@link MediaCodec.Callback}
- * callbacks will be routed to a dedicated Thread. This mode requires API level ≥ 23;
- * if the API level is ≤ 22, the operation mode will be set to {@link
- * MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
- * <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}:
- * Same as {@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} but
- * it will internally use a finer grained locking mechanism for increased performance.
+ * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}: The {@link MediaCodec} will
+ * operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
+ * to a dedicated thread. This mode requires API level ≥ 23; if the API level is ≤
+ * 22, the operation mode will be set to {@link #OPERATION_MODE_SYNCHRONOUS}.
+ * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}: Same as {@link
+ * #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} and, in addition, input buffers will
+ * submitted to the {@link MediaCodec} in a separate thread.
+ * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING}: Same as
+ * {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} and, in addition, input buffers
+ * will be submitted to the {@link MediaCodec} in a separate thread.
+ * <li>{@link
+ * #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING}: Same
+ * as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK} and, in addition,
+ * input buffers will be submitted to the {@link MediaCodec} in a separate thread.
* </ul>
* By default, the operation mode is set to {@link
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
@@ -517,7 +516,7 @@
@Capabilities
public final int supportsFormat(Format format) throws ExoPlaybackException {
try {
- return supportsFormat(mediaCodecSelector, drmSessionManager, format);
+ return supportsFormat(mediaCodecSelector, format);
} catch (DecoderQueryException e) {
throw createRendererException(e, format);
}
@@ -527,7 +526,6 @@
* Returns the {@link Capabilities} for the given {@link Format}.
*
* @param mediaCodecSelector The decoder selector.
- * @param drmSessionManager The renderer's {@link DrmSessionManager}.
* @param format The {@link Format}.
* @return The {@link Capabilities} for this {@link Format}.
* @throws DecoderQueryException If there was an error querying decoders.
@@ -535,7 +533,6 @@
@Capabilities
protected abstract int supportsFormat(
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
Format format)
throws DecoderQueryException;
@@ -580,9 +577,10 @@
String mimeType = inputFormat.sampleMimeType;
if (codecDrmSession != null) {
if (mediaCrypto == null) {
- FrameworkMediaCrypto sessionMediaCrypto = codecDrmSession.getMediaCrypto();
+ @Nullable
+ FrameworkMediaCrypto sessionMediaCrypto = getFrameworkMediaCrypto(codecDrmSession);
if (sessionMediaCrypto == null) {
- DrmSessionException drmError = codecDrmSession.getError();
+ @Nullable DrmSessionException drmError = codecDrmSession.getError();
if (drmError != null) {
// Continue for now. We may be able to avoid failure if the session recovers, or if a
// new input format causes the session to be replaced before it's used.
@@ -647,6 +645,11 @@
}
@Nullable
+ protected final Format getCurrentOutputFormat() {
+ return outputFormat;
+ }
+
+ @Nullable
protected final MediaCodec getCodec() {
return codec;
}
@@ -657,17 +660,47 @@
}
@Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
decoderCounters = new DecoderCounters();
}
@Override
+ protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
+ if (outputStreamOffsetUs == C.TIME_UNSET) {
+ outputStreamOffsetUs = offsetUs;
+ } else {
+ if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) {
+ Log.w(
+ TAG,
+ "Too many stream changes, so dropping offset: "
+ + pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
+ } else {
+ pendingOutputStreamOffsetCount++;
+ }
+ pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs;
+ pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] =
+ largestQueuedPresentationTimeUs;
+ }
+ }
+
+ @Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
inputStreamEnded = false;
outputStreamEnded = false;
pendingOutputEndOfStream = false;
flushOrReinitializeCodec();
+ // If there is a format change on the input side still pending propagation to the output, we
+ // need to queue a format next time a buffer is read. This is because we may not read a new
+ // input format after the position reset.
+ if (formatQueue.size() > 0) {
+ waitingForFirstSampleInFormat = true;
+ }
formatQueue.clear();
+ if (pendingOutputStreamOffsetCount != 0) {
+ outputStreamOffsetUs = pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1];
+ pendingOutputStreamOffsetCount = 0;
+ }
}
@Override
@@ -683,6 +716,8 @@
@Override
protected void onDisabled() {
inputFormat = null;
+ outputStreamOffsetUs = C.TIME_UNSET;
+ pendingOutputStreamOffsetCount = 0;
if (sourceDrmSession != null || codecDrmSession != null) {
// TODO: Do something better with this case.
onReset();
@@ -752,10 +787,11 @@
// We have a format.
maybeInitCodec();
if (codec != null) {
- long drainStartTimeMs = SystemClock.elapsedRealtime();
+ long renderStartTimeMs = SystemClock.elapsedRealtime();
TraceUtil.beginSection("drainAndFeed");
- while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
- while (feedInputBuffer() && shouldContinueFeeding(drainStartTimeMs)) {}
+ while (drainOutputBuffer(positionUs, elapsedRealtimeUs)
+ && shouldContinueRendering(renderStartTimeMs)) {}
+ while (feedInputBuffer() && shouldContinueRendering(renderStartTimeMs)) {}
TraceUtil.endSection();
} else {
decoderCounters.skippedInputBufferCount += skipSource(positionUs);
@@ -804,6 +840,7 @@
}
if (codecDrainAction == DRAIN_ACTION_REINITIALIZE
|| codecNeedsFlushWorkaround
+ || (codecNeedsSosFlushWorkaround && !codecHasOutputMediaFormat)
|| (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
releaseCodec();
return true;
@@ -855,11 +892,13 @@
availableCodecInfos = null;
codecInfo = null;
codecFormat = null;
+ codecHasOutputMediaFormat = false;
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsReconfigureWorkaround = false;
codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false;
+ codecNeedsSosFlushWorkaround = false;
codecNeedsEosFlushWorkaround = false;
codecNeedsEosOutputExceptionWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false;
@@ -870,15 +909,16 @@
mediaCryptoRequiresSecureDecoder = false;
}
- protected DecoderException createDecoderException(
+ protected MediaCodecDecoderException createDecoderException(
Throwable cause, @Nullable MediaCodecInfo codecInfo) {
- return new DecoderException(cause, codecInfo);
+ return new MediaCodecDecoderException(cause, codecInfo);
}
/** Reads into {@link #flagsOnlyBuffer} and returns whether a {@link Format} was read. */
private boolean readToFlagsOnlyBuffer(boolean requireFormat) throws ExoPlaybackException {
FormatHolder formatHolder = getFormatHolder();
flagsOnlyBuffer.clear();
+ @SampleStream.ReadDataResult
int result = readSource(formatHolder, flagsOnlyBuffer, requireFormat);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder);
@@ -1004,6 +1044,18 @@
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
&& Util.SDK_INT >= 23) {
codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
+ } else if (mediaCodecOperationMode
+ == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING
+ && Util.SDK_INT >= 23) {
+ codecAdapter =
+ new DedicatedThreadAsyncMediaCodecAdapter(
+ codec, /* enableAsynchronousQueueing= */ true, getTrackType());
+ } else if (mediaCodecOperationMode
+ == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING
+ && Util.SDK_INT >= 23) {
+ codecAdapter =
+ new MultiLockAsyncMediaCodecAdapter(
+ codec, /* enableAsynchronousQueueing= */ true, getTrackType());
} else {
codecAdapter = new SynchronousMediaCodecAdapter(codec);
}
@@ -1037,6 +1089,7 @@
codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName);
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, codecFormat);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
+ codecNeedsSosFlushWorkaround = codecNeedsSosFlushWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround =
@@ -1052,9 +1105,9 @@
onCodecInitialized(codecName, codecInitializedTimestamp, elapsed);
}
- private boolean shouldContinueFeeding(long drainStartTimeMs) {
+ private boolean shouldContinueRendering(long renderStartTimeMs) {
return renderTimeLimitMs == C.TIME_UNSET
- || SystemClock.elapsedRealtime() - drainStartTimeMs < renderTimeLimitMs;
+ || SystemClock.elapsedRealtime() - renderStartTimeMs < renderTimeLimitMs;
}
private void getCodecBuffers(MediaCodec codec) {
@@ -1101,12 +1154,12 @@
outputBuffer = null;
}
- private void setSourceDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) {
+ private void setSourceDrmSession(@Nullable DrmSession session) {
DrmSession.replaceSession(sourceDrmSession, session);
sourceDrmSession = session;
}
- private void setCodecDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) {
+ private void setCodecDrmSession(@Nullable DrmSession session) {
DrmSession.replaceSession(codecDrmSession, session);
codecDrmSession = session;
}
@@ -1152,7 +1205,7 @@
return true;
}
- int result;
+ @SampleStream.ReadDataResult int result;
FormatHolder formatHolder = getFormatHolder();
int adaptiveReconfigurationBytes = 0;
if (waitingForKeys) {
@@ -1259,8 +1312,8 @@
onQueueInputBuffer(buffer);
if (bufferEncrypted) {
- MediaCodec.CryptoInfo cryptoInfo = getFrameworkCryptoInfo(buffer,
- adaptiveReconfigurationBytes);
+ CryptoInfo cryptoInfo = buffer.cryptoInfo;
+ cryptoInfo.increaseClearDataFirstSubSampleBy(adaptiveReconfigurationBytes);
codecAdapter.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0);
} else {
codecAdapter.queueInputBuffer(inputIndex, 0, buffer.data.limit(), presentationTimeUs, 0);
@@ -1277,8 +1330,7 @@
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
if (codecDrmSession == null
- || (!bufferEncrypted
- && (playClearSamplesWithoutKeys || codecDrmSession.playClearSamplesWithoutKeys()))) {
+ || (!bufferEncrypted && codecDrmSession.playClearSamplesWithoutKeys())) {
return false;
}
@DrmSession.State int drmSessionState = codecDrmSession.getState();
@@ -1309,16 +1361,10 @@
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
*/
- @SuppressWarnings("unchecked")
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
waitingForFirstSampleInFormat = true;
Format newFormat = Assertions.checkNotNull(formatHolder.format);
- if (formatHolder.includesDrmSession) {
- setSourceDrmSession((DrmSession<FrameworkMediaCrypto>) formatHolder.drmSession);
- } else {
- sourceDrmSession =
- getUpdatedSourceDrmSession(inputFormat, newFormat, drmSessionManager, sourceDrmSession);
- }
+ setSourceDrmSession(formatHolder.drmSession);
inputFormat = newFormat;
if (codec == null) {
@@ -1331,7 +1377,9 @@
if ((sourceDrmSession == null && codecDrmSession != null)
|| (sourceDrmSession != null && codecDrmSession == null)
- || (sourceDrmSession != null && !codecInfo.secure)
+ || (sourceDrmSession != codecDrmSession
+ && !codecInfo.secure
+ && maybeRequiresSecureDecoder(sourceDrmSession, newFormat))
|| (Util.SDK_INT < 23 && sourceDrmSession != codecDrmSession)) {
// We might need to switch between the clear and protected output paths, or we're using DRM
// prior to API level 23 where the codec needs to be re-initialized to switch to the new DRM
@@ -1423,12 +1471,33 @@
/**
* Called when an output buffer is successfully processed.
- * <p>
- * The default implementation is a no-op.
*
* @param presentationTimeUs The timestamp associated with the output buffer.
*/
+ @CallSuper
protected void onProcessedOutputBuffer(long presentationTimeUs) {
+ while (pendingOutputStreamOffsetCount != 0
+ && presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) {
+ outputStreamOffsetUs = pendingOutputStreamOffsetsUs[0];
+ pendingOutputStreamOffsetCount--;
+ System.arraycopy(
+ pendingOutputStreamOffsetsUs,
+ /* srcPos= */ 1,
+ pendingOutputStreamOffsetsUs,
+ /* destPos= */ 0,
+ pendingOutputStreamOffsetCount);
+ System.arraycopy(
+ pendingOutputStreamSwitchTimesUs,
+ /* srcPos= */ 1,
+ pendingOutputStreamSwitchTimesUs,
+ /* destPos= */ 0,
+ pendingOutputStreamOffsetCount);
+ onProcessedStreamChange();
+ }
+ }
+
+ /** Called after the last output buffer before a stream change has been processed. */
+ protected void onProcessedStreamChange() {
// Do nothing.
}
@@ -1631,6 +1700,7 @@
outputBuffer,
outputIndex,
outputBufferInfo.flags,
+ /* sampleCount= */ 1,
outputBufferInfo.presentationTimeUs,
isDecodeOnlyOutputBuffer,
isLastOutputBuffer,
@@ -1652,6 +1722,7 @@
outputBuffer,
outputIndex,
outputBufferInfo.flags,
+ /* sampleCount= */ 1,
outputBufferInfo.presentationTimeUs,
isDecodeOnlyOutputBuffer,
isLastOutputBuffer,
@@ -1673,6 +1744,7 @@
/** Processes a new output {@link MediaFormat}. */
private void processOutputMediaFormat() throws ExoPlaybackException {
+ codecHasOutputMediaFormat = true;
MediaFormat mediaFormat = codecAdapter.getOutputFormat();
if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
&& mediaFormat.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
@@ -1719,6 +1791,8 @@
* @param buffer The output buffer to process.
* @param bufferIndex The index of the output buffer.
* @param bufferFlags The flags attached to the output buffer.
+ * @param sampleCount The number of samples extracted from the sample queue in the buffer. This
+ * allows handling multiple samples as a batch for efficiency.
* @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds.
* @param isDecodeOnlyBuffer Whether the buffer was marked with {@link C#BUFFER_FLAG_DECODE_ONLY}
* by the source.
@@ -1734,6 +1808,7 @@
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
+ int sampleCount,
long bufferPresentationTimeUs,
boolean isDecodeOnlyBuffer,
boolean isLastBuffer,
@@ -1756,6 +1831,7 @@
*
* @throws ExoPlaybackException If an error occurs processing the signal.
*/
+ @TargetApi(23) // codecDrainAction == DRAIN_ACTION_UPDATE_DRM_SESSION implies SDK_INT >= 23.
private void processEndOfStream() throws ExoPlaybackException {
switch (codecDrainAction) {
case DRAIN_ACTION_REINITIALIZE:
@@ -1783,14 +1859,82 @@
pendingOutputEndOfStream = true;
}
+ /** Returns the largest queued input presentation time, in microseconds. */
+ protected final long getLargestQueuedPresentationTimeUs() {
+ return largestQueuedPresentationTimeUs;
+ }
+
+ /**
+ * Returns the offset that should be subtracted from {@code bufferPresentationTimeUs} in {@link
+ * #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, int, long, boolean, boolean,
+ * Format)} to get the playback position with respect to the media.
+ */
+ protected final long getOutputStreamOffsetUs() {
+ return outputStreamOffsetUs;
+ }
+
+ /** Returns whether this renderer supports the given {@link Format Format's} DRM scheme. */
+ protected static boolean supportsFormatDrm(Format format) {
+ return format.drmInitData == null
+ || FrameworkMediaCrypto.class.equals(format.exoMediaCryptoType);
+ }
+
+ /**
+ * Returns whether a {@link DrmSession} may require a secure decoder for a given {@link Format}.
+ *
+ * @param drmSession The {@link DrmSession}.
+ * @param format The {@link Format}.
+ * @return Whether a secure decoder may be required.
+ */
+ private boolean maybeRequiresSecureDecoder(DrmSession drmSession, Format format)
+ throws ExoPlaybackException {
+ // MediaCrypto type is checked during track selection.
+ @Nullable FrameworkMediaCrypto sessionMediaCrypto = getFrameworkMediaCrypto(drmSession);
+ if (sessionMediaCrypto == null) {
+ // We'd only expect this to happen if the CDM from which the pending session is obtained needs
+ // provisioning. This is unlikely to happen (it probably requires a switch from one DRM scheme
+ // to another, where the new CDM hasn't been used before and needs provisioning). Assume that
+ // a secure decoder may be required.
+ return true;
+ }
+ if (sessionMediaCrypto.forceAllowInsecureDecoderComponents) {
+ return false;
+ }
+ MediaCrypto mediaCrypto;
+ try {
+ mediaCrypto = new MediaCrypto(sessionMediaCrypto.uuid, sessionMediaCrypto.sessionId);
+ } catch (MediaCryptoException e) {
+ // This shouldn't happen, but if it does then assume that a secure decoder may be required.
+ return true;
+ }
+ try {
+ return mediaCrypto.requiresSecureDecoderComponent(format.sampleMimeType);
+ } finally {
+ mediaCrypto.release();
+ }
+ }
+
private void reinitializeCodec() throws ExoPlaybackException {
releaseCodec();
maybeInitCodec();
}
- @TargetApi(23)
+ private boolean isDecodeOnlyBuffer(long presentationTimeUs) {
+ // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would
+ // box presentationTimeUs, creating a Long object that would need to be garbage collected.
+ int size = decodeOnlyPresentationTimestamps.size();
+ for (int i = 0; i < size; i++) {
+ if (decodeOnlyPresentationTimestamps.get(i) == presentationTimeUs) {
+ decodeOnlyPresentationTimestamps.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @RequiresApi(23)
private void updateDrmSessionOrReinitializeCodecV23() throws ExoPlaybackException {
- FrameworkMediaCrypto sessionMediaCrypto = sourceDrmSession.getMediaCrypto();
+ @Nullable FrameworkMediaCrypto sessionMediaCrypto = getFrameworkMediaCrypto(sourceDrmSession);
if (sessionMediaCrypto == null) {
// We'd only expect this to happen if the CDM from which the pending session is obtained needs
// provisioning. This is unlikely to happen (it probably requires a switch from one DRM scheme
@@ -1824,33 +1968,18 @@
codecDrainAction = DRAIN_ACTION_NONE;
}
- private boolean isDecodeOnlyBuffer(long presentationTimeUs) {
- // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would
- // box presentationTimeUs, creating a Long object that would need to be garbage collected.
- int size = decodeOnlyPresentationTimestamps.size();
- for (int i = 0; i < size; i++) {
- if (decodeOnlyPresentationTimestamps.get(i) == presentationTimeUs) {
- decodeOnlyPresentationTimestamps.remove(i);
- return true;
- }
+ @Nullable
+ private FrameworkMediaCrypto getFrameworkMediaCrypto(DrmSession drmSession)
+ throws ExoPlaybackException {
+ @Nullable ExoMediaCrypto mediaCrypto = drmSession.getMediaCrypto();
+ if (mediaCrypto != null && !(mediaCrypto instanceof FrameworkMediaCrypto)) {
+ // This should not happen if the track went through a supportsFormatDrm() check, during track
+ // selection.
+ throw createRendererException(
+ new IllegalArgumentException("Expecting FrameworkMediaCrypto but found: " + mediaCrypto),
+ inputFormat);
}
- return false;
- }
-
- private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(
- DecoderInputBuffer buffer, int adaptiveReconfigurationBytes) {
- MediaCodec.CryptoInfo cryptoInfo = buffer.cryptoInfo.getFrameworkCryptoInfo();
- if (adaptiveReconfigurationBytes == 0) {
- return cryptoInfo;
- }
- // There must be at least one sub-sample, although numBytesOfClearData is permitted to be
- // null if it contains no clear data. Instantiate it if needed, and add the reconfiguration
- // bytes to the clear byte count of the first sub-sample.
- if (cryptoInfo.numBytesOfClearData == null) {
- cryptoInfo.numBytesOfClearData = new int[1];
- }
- cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes;
- return cryptoInfo;
+ return (FrameworkMediaCrypto) mediaCrypto;
}
private static boolean isMediaCodecException(IllegalStateException error) {
@@ -1861,7 +1990,7 @@
return stackTrace.length > 0 && stackTrace[0].getClassName().equals("android.media.MediaCodec");
}
- @TargetApi(21)
+ @RequiresApi(21)
private static boolean isMediaCodecExceptionV21(IllegalStateException error) {
return error instanceof MediaCodec.CodecException;
}
@@ -2017,4 +2146,21 @@
return Util.SDK_INT <= 18 && format.channelCount == 1
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
}
+
+ /**
+ * Returns whether the decoder is known to behave incorrectly if flushed prior to having output a
+ * {@link MediaFormat}.
+ *
+ * <p>If true is returned, the renderer will work around the issue by instantiating a new decoder
+ * when this case occurs.
+ *
+ * <p>See [Internal: b/141097367].
+ *
+ * @param name The name of the decoder.
+ * @return True if the decoder is known to behave incorrectly if flushed prior to having output a
+ * {@link MediaFormat}. False otherwise.
+ */
+ private static boolean codecNeedsSosFlushWorkaround(String name) {
+ return Util.SDK_INT == 29 && "c2.android.aac.decoder".equals(name);
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java
index 10ff811..7f39dce 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
-import androidx.annotation.Nullable;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import java.util.List;
@@ -29,22 +28,7 @@
* Default implementation of {@link MediaCodecSelector}, which returns the preferred decoder for
* the given format.
*/
- MediaCodecSelector DEFAULT =
- new MediaCodecSelector() {
- @Override
- public List<MediaCodecInfo> getDecoderInfos(
- String mimeType, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder)
- throws DecoderQueryException {
- return MediaCodecUtil.getDecoderInfos(
- mimeType, requiresSecureDecoder, requiresTunnelingDecoder);
- }
-
- @Override
- @Nullable
- public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
- return MediaCodecUtil.getPassthroughDecoderInfo();
- }
- };
+ MediaCodecSelector DEFAULT = MediaCodecUtil::getDecoderInfos;
/**
* Returns a list of decoders that can decode media in the specified MIME type, in priority order.
@@ -59,13 +43,4 @@
List<MediaCodecInfo> getDecoderInfos(
String mimeType, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder)
throws DecoderQueryException;
-
- /**
- * Selects a decoder to instantiate for audio passthrough.
- *
- * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists.
- * @throws DecoderQueryException Thrown if there was an error querying decoders.
- */
- @Nullable
- MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
index b187e2c..db68fb3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.mediacodec;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
@@ -25,6 +24,7 @@
import android.util.SparseIntArray;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Log;
@@ -123,10 +123,7 @@
*/
@Nullable
public static MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
- @Nullable
- MediaCodecInfo decoderInfo =
- getDecoderInfo(MimeTypes.AUDIO_RAW, /* secure= */ false, /* tunneling= */ false);
- return decoderInfo == null ? null : MediaCodecInfo.newPassthroughInstance(decoderInfo.name);
+ return getDecoderInfo(MimeTypes.AUDIO_RAW, /* secure= */ false, /* tunneling= */ false);
}
/**
@@ -289,9 +286,16 @@
// Note: MediaCodecList is sorted by the framework such that the best decoders come first.
for (int i = 0; i < numberOfCodecs; i++) {
android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i);
+ if (isAlias(codecInfo)) {
+ // Skip aliases of other codecs, since they will also be listed under their canonical
+ // names.
+ continue;
+ }
String name = codecInfo.getName();
- @Nullable
- String codecMimeType = getCodecMimeType(codecInfo, name, secureDecodersExplicit, mimeType);
+ if (!isCodecUsableDecoder(codecInfo, name, secureDecodersExplicit, mimeType)) {
+ continue;
+ }
+ @Nullable String codecMimeType = getCodecMimeType(codecInfo, name, mimeType);
if (codecMimeType == null) {
continue;
}
@@ -373,7 +377,6 @@
*
* @param info The codec information.
* @param name The name of the codec
- * @param secureDecodersExplicit Whether secure decoders were explicitly listed, if present.
* @param mimeType The MIME type.
* @return The codec's supported MIME type for media of type {@code mimeType}, or {@code null} if
* the codec can't be used. If non-null, the returned type will be equal to {@code mimeType}
@@ -383,12 +386,7 @@
private static String getCodecMimeType(
android.media.MediaCodecInfo info,
String name,
- boolean secureDecodersExplicit,
String mimeType) {
- if (!isCodecUsableDecoder(info, name, secureDecodersExplicit, mimeType)) {
- return null;
- }
-
String[] supportedTypes = info.getSupportedTypes();
for (String supportedType : supportedTypes) {
if (supportedType.equalsIgnoreCase(mimeType)) {
@@ -591,6 +589,15 @@
}
}
+ private static boolean isAlias(android.media.MediaCodecInfo info) {
+ return Util.SDK_INT >= 29 && isAliasV29(info);
+ }
+
+ @RequiresApi(29)
+ private static boolean isAliasV29(android.media.MediaCodecInfo info) {
+ return info.isAlias();
+ }
+
/**
* The result of {@link android.media.MediaCodecInfo#isHardwareAccelerated()} for API levels 29+,
* or a best-effort approximation for lower levels.
@@ -604,7 +611,7 @@
return !isSoftwareOnly(codecInfo);
}
- @TargetApi(29)
+ @RequiresApi(29)
private static boolean isHardwareAcceleratedV29(android.media.MediaCodecInfo codecInfo) {
return codecInfo.isHardwareAccelerated();
}
@@ -630,7 +637,7 @@
|| (!codecName.startsWith("omx.") && !codecName.startsWith("c2."));
}
- @TargetApi(29)
+ @RequiresApi(29)
private static boolean isSoftwareOnlyV29(android.media.MediaCodecInfo codecInfo) {
return codecInfo.isSoftwareOnly();
}
@@ -649,7 +656,7 @@
&& !codecName.startsWith("c2.google.");
}
- @TargetApi(29)
+ @RequiresApi(29)
private static boolean isVendorV29(android.media.MediaCodecInfo codecInfo) {
return codecInfo.isVendor();
}
@@ -947,7 +954,7 @@
boolean isFeatureRequired(String feature, String mimeType, CodecCapabilities capabilities);
}
- @TargetApi(21)
+ @RequiresApi(21)
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
private final int codecKind;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java
index d04b9ce..d51f985 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java
@@ -22,19 +22,22 @@
import android.os.HandlerThread;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.util.IntArrayQueue;
import com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in asynchronous mode
- * and routes {@link MediaCodec.Callback} callbacks on a dedicated Thread that is managed
+ * and routes {@link MediaCodec.Callback} callbacks on a dedicated thread that is managed
* internally.
*
* <p>The main difference of this class compared to the {@link
@@ -42,8 +45,8 @@
* locking. The {@link DedicatedThreadAsyncMediaCodecAdapter} uses a single lock to synchronize
* access, whereas this class uses a different lock to access the available input and available
* output buffer indexes returned from the {@link MediaCodec}. This class assumes that the {@link
- * MediaCodecAdapter} methods will be accessed by the Playback Thread and the {@link
- * MediaCodec.Callback} methods will be accessed by the internal Thread. This class is
+ * MediaCodecAdapter} methods will be accessed by the playback thread and the {@link
+ * MediaCodec.Callback} methods will be accessed by the internal thread. This class is
* <strong>NOT</strong> generally thread-safe in the sense that its public methods cannot be called
* by any thread.
*/
@@ -51,6 +54,8 @@
/* package */ final class MultiLockAsyncMediaCodecAdapter extends MediaCodec.Callback
implements MediaCodecAdapter {
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_CREATED, STATE_STARTED, STATE_SHUT_DOWN})
private @interface State {}
@@ -85,20 +90,54 @@
@Nullable
private IllegalStateException codecException;
- @GuardedBy("objectStateLock")
- private @State int state;
-
private final HandlerThread handlerThread;
private @MonotonicNonNull Handler handler;
private Runnable codecStartRunnable;
+ private final MediaCodecInputBufferEnqueuer bufferEnqueuer;
- /** Creates a new instance that wraps the specified {@link MediaCodec}. */
+ @GuardedBy("objectStateLock")
+ @State
+ private int state;
+
+ /**
+ * Creates a new instance that wraps the specified {@link MediaCodec}. An instance created with
+ * this constructor will queue input buffers synchronously.
+ *
+ * @param codec The {@link MediaCodec} to wrap.
+ * @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
+ * labelling the internal thread accordingly.
+ */
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
- this(codec, new HandlerThread(createThreadLabel(trackType)));
+ this(
+ codec,
+ /* enableAsynchronousQueueing= */ false,
+ trackType,
+ new HandlerThread(createThreadLabel(trackType)));
+ }
+
+ /**
+ * Creates a new instance that wraps the specified {@link MediaCodec}.
+ *
+ * @param codec The {@link MediaCodec} to wrap.
+ * @param enableAsynchronousQueueing Whether input buffers will be queued asynchronously.
+ * @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
+ * labelling the internal thread accordingly.
+ */
+ /* package */ MultiLockAsyncMediaCodecAdapter(
+ MediaCodec codec, boolean enableAsynchronousQueueing, int trackType) {
+ this(
+ codec,
+ enableAsynchronousQueueing,
+ trackType,
+ new HandlerThread(createThreadLabel(trackType)));
}
@VisibleForTesting
- /* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, HandlerThread handlerThread) {
+ /* package */ MultiLockAsyncMediaCodecAdapter(
+ MediaCodec codec,
+ boolean enableAsynchronousQueueing,
+ int trackType,
+ HandlerThread handlerThread) {
this.codec = codec;
inputBufferLock = new Object();
outputBufferLock = new Object();
@@ -108,9 +147,14 @@
bufferInfos = new ArrayDeque<>();
formats = new ArrayDeque<>();
codecException = null;
- state = STATE_CREATED;
this.handlerThread = handlerThread;
codecStartRunnable = codec::start;
+ if (enableAsynchronousQueueing) {
+ bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType);
+ } else {
+ bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(codec);
+ }
+ state = STATE_CREATED;
}
@Override
@@ -119,6 +163,7 @@
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler);
+ bufferEnqueuer.start();
codecStartRunnable.run();
state = STATE_STARTED;
}
@@ -164,20 +209,21 @@
int index, int offset, int size, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it is not interacting with
// MediaCodec.Callback and dequeueing buffers operations.
- codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ bufferEnqueuer.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
}
@Override
public void queueSecureInputBuffer(
- int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags) {
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it is not interacting with
// MediaCodec.Callback and dequeueing buffers operations.
- codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
}
@Override
public void flush() {
synchronized (objectStateLock) {
+ bufferEnqueuer.flush();
codec.flush();
pendingFlush++;
Util.castNonNull(handler).post(this::onFlushComplete);
@@ -188,6 +234,7 @@
public void shutdown() {
synchronized (objectStateLock) {
if (state == STATE_STARTED) {
+ bufferEnqueuer.shutdown();
handlerThread.quit();
}
state = STATE_SHUT_DOWN;
@@ -244,18 +291,17 @@
}
}
- // Called by the internal Thread.
+ // Called by the internal thread.
@Override
- public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
+ public void onInputBufferAvailable(MediaCodec codec, int index) {
synchronized (inputBufferLock) {
availableInputBuffers.add(index);
}
}
@Override
- public void onOutputBufferAvailable(
- @NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
+ public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
synchronized (outputBufferLock) {
availableOutputBuffers.add(index);
bufferInfos.add(info);
@@ -263,12 +309,12 @@
}
@Override
- public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
+ public void onError(MediaCodec codec, MediaCodec.CodecException e) {
onMediaCodecError(e);
}
@Override
- public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
+ public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
synchronized (outputBufferLock) {
availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
formats.add(format);
@@ -326,7 +372,7 @@
}
private static String createThreadLabel(int trackType) {
- StringBuilder labelBuilder = new StringBuilder("MediaCodecAsyncAdapter:");
+ StringBuilder labelBuilder = new StringBuilder("ExoPlayer:MediaCodecAsyncAdapter:");
if (trackType == C.TRACK_TYPE_AUDIO) {
labelBuilder.append("Audio");
} else if (trackType == C.TRACK_TYPE_VIDEO) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java
index 84d2ba1..f50b49e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java
@@ -18,6 +18,7 @@
import android.media.MediaCodec;
import android.media.MediaFormat;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
/**
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode.
@@ -58,8 +59,9 @@
@Override
public void queueSecureInputBuffer(
- int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags) {
- codec.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
+ codec.queueSecureInputBuffer(
+ index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags);
}
@Override
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java
new file mode 100644
index 0000000..f16748f
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.mediacodec;
+
+import android.media.MediaCodec;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
+
+/**
+ * A {@link MediaCodecInputBufferEnqueuer} that forwards queueing methods directly to {@link
+ * MediaCodec}.
+ */
+class SynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnqueuer {
+ private final MediaCodec codec;
+
+ /**
+ * Creates an instance that queues input buffers on the specified {@link MediaCodec}.
+ *
+ * @param codec The {@link MediaCodec} to submit input buffers to.
+ */
+ SynchronousMediaCodecBufferEnqueuer(MediaCodec codec) {
+ this.codec = codec;
+ }
+
+ @Override
+ public void start() {}
+
+ @Override
+ public void queueInputBuffer(
+ int index, int offset, int size, long presentationTimeUs, int flags) {
+ codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ }
+
+ @Override
+ public void queueSecureInputBuffer(
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
+ codec.queueSecureInputBuffer(
+ index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags);
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void shutdown() {}
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java
index 0b65383..fae53a5 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java
@@ -17,6 +17,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.metadata.dvbsi.AppInfoTableDecoder;
import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder;
import com.google.android.exoplayer2.metadata.icy.IcyDecoder;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
@@ -67,7 +68,8 @@
return MimeTypes.APPLICATION_ID3.equals(mimeType)
|| MimeTypes.APPLICATION_EMSG.equals(mimeType)
|| MimeTypes.APPLICATION_SCTE35.equals(mimeType)
- || MimeTypes.APPLICATION_ICY.equals(mimeType);
+ || MimeTypes.APPLICATION_ICY.equals(mimeType)
+ || MimeTypes.APPLICATION_AIT.equals(mimeType);
}
@Override
@@ -83,6 +85,8 @@
return new SpliceInfoDecoder();
case MimeTypes.APPLICATION_ICY:
return new IcyDecoder();
+ case MimeTypes.APPLICATION_AIT:
+ return new AppInfoTableDecoder();
default:
break;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
index 7a5235a..238d515 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java
@@ -27,6 +27,7 @@
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
@@ -39,6 +40,7 @@
*/
public final class MetadataRenderer extends BaseRenderer implements Callback {
+ private static final String TAG = "MetadataRenderer";
private static final int MSG_INVOKE_RENDERER = 0;
// TODO: Holding multiple pending metadata objects is temporary mitigation against
// https://github.com/google/ExoPlayer/issues/1874. It should be removed once this issue has been
@@ -92,11 +94,16 @@
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
public int supportsFormat(Format format) {
if (decoderFactory.supportsFormat(format)) {
return RendererCapabilities.create(
- supportsFormatDrm(null, format.drmInitData) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_DRM);
+ format.drmInitData == null ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_DRM);
} else {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
}
@@ -118,7 +125,7 @@
if (!inputStreamEnded && pendingMetadataCount < MAX_PENDING_METADATA_COUNT) {
buffer.clear();
FormatHolder formatHolder = getFormatHolder();
- int result = readSource(formatHolder, buffer, false);
+ @SampleStream.ReadDataResult int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) {
inputStreamEnded = true;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTable.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTable.java
new file mode 100644
index 0000000..cdfb15f
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTable.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.metadata.dvbsi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.util.Assertions;
+
+/**
+ * A representation of a DVB Application Information Table (AIT).
+ *
+ * <p>For more info on the AIT see section 5.3.4 of the <a
+ * href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
+ * DVB ETSI TS 102 809 v1.1.1 spec</a>.
+ */
+public final class AppInfoTable implements Metadata.Entry {
+ /**
+ * The application shall be started when the service is selected, unless the application is
+ * already running.
+ */
+ public static final int CONTROL_CODE_AUTOSTART = 0x01;
+ /**
+ * The application is allowed to run while the service is selected, however it shall not start
+ * automatically when the service becomes selected.
+ */
+ public static final int CONTROL_CODE_PRESENT = 0x02;
+
+ public final int controlCode;
+ public final String url;
+
+ public AppInfoTable(int controlCode, String url) {
+ this.controlCode = controlCode;
+ this.url = url;
+ }
+
+ @Override
+ public String toString() {
+ return "Ait(controlCode=" + controlCode + ",url=" + url + ")";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(url);
+ parcel.writeInt(controlCode);
+ }
+
+ public static final Parcelable.Creator<AppInfoTable> CREATOR =
+ new Parcelable.Creator<AppInfoTable>() {
+ @Override
+ public AppInfoTable createFromParcel(Parcel in) {
+ String url = Assertions.checkNotNull(in.readString());
+ int controlCode = in.readInt();
+ return new AppInfoTable(controlCode, url);
+ }
+
+ @Override
+ public AppInfoTable[] newArray(int size) {
+ return new AppInfoTable[size];
+ }
+ };
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoder.java
new file mode 100644
index 0000000..f533b97
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoder.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.metadata.dvbsi;
+
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.metadata.MetadataDecoder;
+import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.ParsableBitArray;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+
+/**
+ * Decoder for the DVB Application Information Table (AIT).
+ *
+ * <p>For more info on the AIT see section 5.3.4 of the <a
+ * href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
+ * DVB ETSI TS 102 809 v1.1.1 spec</a>.
+ */
+public final class AppInfoTableDecoder implements MetadataDecoder {
+
+ /** See section 5.3.6. */
+ private static final int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02;
+ /** See section 5.3.7. */
+ private static final int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15;
+
+ /** See table 29 in section 5.3.6. */
+ private static final int TRANSPORT_PROTOCOL_HTTP = 3;
+
+ /** See table 16 in section 5.3.4.6. */
+ public static final int APPLICATION_INFORMATION_TABLE_ID = 0x74;
+
+ @Override
+ @Nullable
+ public Metadata decode(MetadataInputBuffer inputBuffer) {
+ ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
+ Assertions.checkArgument(
+ buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
+ int tableId = buffer.get();
+ return tableId == APPLICATION_INFORMATION_TABLE_ID
+ ? parseAit(new ParsableBitArray(buffer.array(), buffer.limit()))
+ : null;
+ }
+
+ @Nullable
+ private static Metadata parseAit(ParsableBitArray sectionData) {
+ // tableId, section_syntax_indication, reserved_future_use, reserved
+ sectionData.skipBits(12);
+ int sectionLength = sectionData.readBits(12);
+ int endOfSection = sectionData.getBytePosition() + sectionLength - 4 /* Ignore leading CRC */;
+
+ // test_application_flag, application_type, reserved, version_number, current_next_indicator,
+ // section_number, last_section_number, reserved_future_use
+ sectionData.skipBits(44);
+
+ int commonDescriptorsLength = sectionData.readBits(12);
+
+ // Since we currently only keep URL and control code, which are unique per application,
+ // there is no useful information in common descriptor.
+ sectionData.skipBytes(commonDescriptorsLength);
+
+ // reserved_future_use, application_loop_length
+ sectionData.skipBits(16);
+
+ ArrayList<AppInfoTable> appInfoTables = new ArrayList<>();
+ while (sectionData.getBytePosition() < endOfSection) {
+ @Nullable String urlBase = null;
+ @Nullable String urlExtension = null;
+
+ // application_identifier
+ sectionData.skipBits(48);
+
+ int controlCode = sectionData.readBits(8);
+
+ // reserved_future_use
+ sectionData.skipBits(4);
+
+ int applicationDescriptorsLoopLength = sectionData.readBits(12);
+ int positionOfNextApplication =
+ sectionData.getBytePosition() + applicationDescriptorsLoopLength;
+ while (sectionData.getBytePosition() < positionOfNextApplication) {
+ int descriptorTag = sectionData.readBits(8);
+ int descriptorLength = sectionData.readBits(8);
+ int positionOfNextDescriptor = sectionData.getBytePosition() + descriptorLength;
+
+ if (descriptorTag == DESCRIPTOR_TRANSPORT_PROTOCOL) {
+ // See section 5.3.6.
+ int protocolId = sectionData.readBits(16);
+ // label
+ sectionData.skipBits(8);
+
+ if (protocolId == TRANSPORT_PROTOCOL_HTTP) {
+ // See section 5.3.6.2.
+ while (sectionData.getBytePosition() < positionOfNextDescriptor) {
+ int urlBaseLength = sectionData.readBits(8);
+ urlBase = sectionData.readBytesAsString(urlBaseLength, Charset.forName(C.ASCII_NAME));
+
+ int extensionCount = sectionData.readBits(8);
+ for (int urlExtensionIndex = 0;
+ urlExtensionIndex < extensionCount;
+ urlExtensionIndex++) {
+ int urlExtensionLength = sectionData.readBits(8);
+ sectionData.skipBytes(urlExtensionLength);
+ }
+ }
+ }
+ } else if (descriptorTag == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) {
+ // See section 5.3.7.
+ urlExtension =
+ sectionData.readBytesAsString(descriptorLength, Charset.forName(C.ASCII_NAME));
+ }
+
+ sectionData.setPosition(positionOfNextDescriptor * 8);
+ }
+
+ sectionData.setPosition(positionOfNextApplication * 8);
+
+ if (urlBase != null && urlExtension != null) {
+ appInfoTables.add(new AppInfoTable(controlCode, urlBase + urlExtension));
+ }
+ }
+
+ return appInfoTables.isEmpty() ? null : new Metadata(appInfoTables);
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/package-info.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/package-info.java
new file mode 100644
index 0000000..33efd26
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+@NonNullApi
+package com.google.android.exoplayer2.metadata.dvbsi;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java
index 854a8fc..cd3c1df 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/icy/IcyDecoder.java
@@ -45,9 +45,10 @@
}
@Override
- @SuppressWarnings("ByteBufferBackingArray")
public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
+ Assertions.checkArgument(
+ buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
@Nullable String icyString = decodeToString(buffer);
byte[] icyBytes = new byte[buffer.limit()];
buffer.get(icyBytes);
@@ -63,13 +64,17 @@
while (matcher.find(index)) {
@Nullable String key = Util.toLowerInvariant(matcher.group(1));
@Nullable String value = matcher.group(2);
- switch (key) {
- case STREAM_KEY_NAME:
- name = value;
- break;
- case STREAM_KEY_URL:
- url = value;
- break;
+ if (key != null) {
+ switch (key) {
+ case STREAM_KEY_NAME:
+ name = value;
+ break;
+ case STREAM_KEY_URL:
+ url = value;
+ break;
+ default:
+ break;
+ }
}
index = matcher.end();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
index 2ac325a..647e129 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
@@ -47,10 +47,11 @@
sectionHeader = new ParsableBitArray();
}
- @SuppressWarnings("ByteBufferBackingArray")
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
+ Assertions.checkArgument(
+ buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
// Internal timestamps adjustment.
if (timestampAdjuster == null
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
index d7a6b4a..8e50d70 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java
@@ -30,7 +30,6 @@
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
@@ -171,8 +170,7 @@
new VideoRendererEventListener() {},
new AudioRendererEventListener() {},
(cues) -> {},
- (metadata) -> {},
- /* drmSessionManager= */ null);
+ (metadata) -> {});
RendererCapabilities[] capabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
capabilities[i] = renderers[i].getCapabilities();
@@ -271,8 +269,8 @@
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
- * @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers created by
- * {@code renderersFactory}.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
* downloading.
* @return A {@link DownloadHelper} for DASH streams.
@@ -282,7 +280,7 @@
Uri uri,
DataSource.Factory dataSourceFactory,
RenderersFactory renderersFactory,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ @Nullable DrmSessionManager drmSessionManager,
DefaultTrackSelector.Parameters trackSelectorParameters) {
return new DownloadHelper(
DownloadRequest.TYPE_DASH,
@@ -341,8 +339,8 @@
* @param dataSourceFactory A {@link DataSource.Factory} used to load the playlist.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
- * @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers created by
- * {@code renderersFactory}.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
* downloading.
* @return A {@link DownloadHelper} for HLS streams.
@@ -352,7 +350,7 @@
Uri uri,
DataSource.Factory dataSourceFactory,
RenderersFactory renderersFactory,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ @Nullable DrmSessionManager drmSessionManager,
DefaultTrackSelector.Parameters trackSelectorParameters) {
return new DownloadHelper(
DownloadRequest.TYPE_HLS,
@@ -411,8 +409,8 @@
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
- * @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers created by
- * {@code renderersFactory}.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
* downloading.
* @return A {@link DownloadHelper} for SmoothStreaming streams.
@@ -422,7 +420,7 @@
Uri uri,
DataSource.Factory dataSourceFactory,
RenderersFactory renderersFactory,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ @Nullable DrmSessionManager drmSessionManager,
DefaultTrackSelector.Parameters trackSelectorParameters) {
return new DownloadHelper(
DownloadRequest.TYPE_SS,
@@ -440,27 +438,27 @@
/**
* Equivalent to {@link #createMediaSource(DownloadRequest, Factory, DrmSessionManager)
- * createMediaSource(downloadRequest, dataSourceFactory,
- * DrmSessionManager.getDummyDrmSessionManager())}.
+ * createMediaSource(downloadRequest, dataSourceFactory, null)}.
*/
public static MediaSource createMediaSource(
DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory) {
- return createMediaSource(
- downloadRequest, dataSourceFactory, DrmSessionManager.getDummyDrmSessionManager());
+ return createMediaSource(downloadRequest, dataSourceFactory, /* drmSessionManager= */ null);
}
/**
- * Utility method to create a MediaSource which only contains the tracks defined in {@code
+ * Utility method to create a {@link MediaSource} that only exposes the tracks defined in {@code
* downloadRequest}.
*
* @param downloadRequest A {@link DownloadRequest}.
* @param dataSourceFactory A factory for {@link DataSource}s to read the media.
- * @return A MediaSource which only contains the tracks defined in {@code downloadRequest}.
+ * @param drmSessionManager An optional {@link DrmSessionManager} to be passed to the {@link
+ * MediaSource}.
+ * @return A {@link MediaSource} that only exposes the tracks defined in {@code downloadRequest}.
*/
public static MediaSource createMediaSource(
DownloadRequest downloadRequest,
DataSource.Factory dataSourceFactory,
- DrmSessionManager<?> drmSessionManager) {
+ @Nullable DrmSessionManager drmSessionManager) {
@Nullable Constructor<? extends MediaSourceFactory> constructor;
switch (downloadRequest.type) {
case DownloadRequest.TYPE_DASH:
@@ -800,7 +798,7 @@
}
// Initialization of array of Lists.
- @SuppressWarnings({"unchecked", "rawtypes"})
+ @SuppressWarnings("unchecked")
private void onMediaPrepared() {
Assertions.checkNotNull(mediaPreparer);
Assertions.checkNotNull(mediaPreparer.mediaPeriods);
@@ -944,7 +942,7 @@
@Nullable Constructor<? extends MediaSourceFactory> constructor,
Uri uri,
Factory dataSourceFactory,
- @Nullable DrmSessionManager<?> drmSessionManager,
+ @Nullable DrmSessionManager drmSessionManager,
@Nullable List<StreamKey> streamKeys) {
if (constructor == null) {
throw new IllegalStateException("Module missing to create media source.");
@@ -995,7 +993,7 @@
@SuppressWarnings("methodref.receiver.bound.invalid")
Handler downloadThreadHandler = Util.createHandler(this::handleDownloadHelperCallbackMessage);
this.downloadHelperHandler = downloadThreadHandler;
- mediaSourceThread = new HandlerThread("DownloadHelper");
+ mediaSourceThread = new HandlerThread("ExoPlayer:DownloadHelper");
mediaSourceThread.start();
mediaSourceHandler = Util.createHandler(mediaSourceThread.getLooper(), /* callback= */ this);
mediaSourceHandler.sendEmptyMessage(MESSAGE_PREPARE_SOURCE);
@@ -1157,8 +1155,8 @@
return C.SELECTION_REASON_UNKNOWN;
}
- @Nullable
@Override
+ @Nullable
public Object getSelectionData() {
return null;
}
@@ -1181,8 +1179,8 @@
return 0;
}
- @Nullable
@Override
+ @Nullable
public TransferListener getTransferListener() {
return null;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
index 66b2a7c..3724701 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
@@ -221,7 +221,7 @@
@SuppressWarnings("methodref.receiver.bound.invalid")
Handler mainHandler = Util.createHandler(this::handleMainMessage);
this.mainHandler = mainHandler;
- HandlerThread internalThread = new HandlerThread("DownloadManager file i/o");
+ HandlerThread internalThread = new HandlerThread("ExoPlayer:DownloadManager");
internalThread.start();
internalHandler =
new InternalHandler(
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
index c9c3275..51f7fa3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
@@ -595,7 +595,7 @@
}
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
lastStartId = startId;
taskRemoved = false;
@Nullable String intentAction = null;
@@ -617,7 +617,9 @@
// Do nothing.
break;
case ACTION_ADD_DOWNLOAD:
- @Nullable DownloadRequest downloadRequest = intent.getParcelableExtra(KEY_DOWNLOAD_REQUEST);
+ @Nullable
+ DownloadRequest downloadRequest =
+ Assertions.checkNotNull(intent).getParcelableExtra(KEY_DOWNLOAD_REQUEST);
if (downloadRequest == null) {
Log.e(TAG, "Ignored ADD_DOWNLOAD: Missing " + KEY_DOWNLOAD_REQUEST + " extra");
} else {
@@ -642,7 +644,7 @@
downloadManager.pauseDownloads();
break;
case ACTION_SET_STOP_REASON:
- if (!intent.hasExtra(KEY_STOP_REASON)) {
+ if (!Assertions.checkNotNull(intent).hasExtra(KEY_STOP_REASON)) {
Log.e(TAG, "Ignored SET_STOP_REASON: Missing " + KEY_STOP_REASON + " extra");
} else {
int stopReason = intent.getIntExtra(KEY_STOP_REASON, /* defaultValue= */ 0);
@@ -650,7 +652,9 @@
}
break;
case ACTION_SET_REQUIREMENTS:
- @Nullable Requirements requirements = intent.getParcelableExtra(KEY_REQUIREMENTS);
+ @Nullable
+ Requirements requirements =
+ Assertions.checkNotNull(intent).getParcelableExtra(KEY_REQUIREMENTS);
if (requirements == null) {
Log.e(TAG, "Ignored SET_REQUIREMENTS: Missing " + KEY_REQUIREMENTS + " extra");
} else {
@@ -693,8 +697,8 @@
/**
* Throws {@link UnsupportedOperationException} because this service is not designed to be bound.
*/
- @Nullable
@Override
+ @Nullable
public final IBinder onBind(Intent intent) {
throw new UnsupportedOperationException();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java
index a732582..055410c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java
@@ -56,12 +56,11 @@
public ProgressiveDownloader(
Uri uri, @Nullable String customCacheKey, DownloaderConstructorHelper constructorHelper) {
this.dataSpec =
- new DataSpec(
- uri,
- /* absoluteStreamPosition= */ 0,
- C.LENGTH_UNSET,
- customCacheKey,
- /* flags= */ DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION);
+ new DataSpec.Builder()
+ .setUri(uri)
+ .setKey(customCacheKey)
+ .setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
+ .build();
this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource();
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
index 5155685..299998e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java
@@ -228,12 +228,7 @@
}
protected static DataSpec getCompressibleDataSpec(Uri uri) {
- return new DataSpec(
- uri,
- /* absoluteStreamPosition= */ 0,
- /* length= */ C.LENGTH_UNSET,
- /* key= */ null,
- /* flags= */ DataSpec.FLAG_ALLOW_GZIP);
+ return new DataSpec.Builder().setUri(uri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build();
}
private static void mergeSegments(List<Segment> segments, CacheKeyFactory keyFactory) {
@@ -267,7 +262,7 @@
private static boolean canMergeSegments(DataSpec dataSpec1, DataSpec dataSpec2) {
return dataSpec1.uri.equals(dataSpec2.uri)
&& dataSpec1.length != C.LENGTH_UNSET
- && (dataSpec1.absoluteStreamPosition + dataSpec1.length == dataSpec2.absoluteStreamPosition)
+ && (dataSpec1.position + dataSpec1.length == dataSpec2.position)
&& Util.areEqual(dataSpec1.key, dataSpec2.key)
&& dataSpec1.flags == dataSpec2.flags
&& dataSpec1.httpMethod == dataSpec2.httpMethod
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
index fcebc93..c4861ab 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.scheduler;
-import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -24,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.PersistableBundle;
+import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
@@ -42,7 +42,7 @@
* android:exported="true"/>
* }</pre>
*/
-@TargetApi(21)
+@RequiresApi(21)
public final class PlatformScheduler implements Scheduler {
private static final boolean DEBUG = false;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
index f55978c..797b7f7 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.scheduler;
-import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -124,7 +123,7 @@
return requirements;
}
- @TargetApi(24)
+ @RequiresApi(24)
private void registerNetworkCallbackV24() {
ConnectivityManager connectivityManager =
Assertions.checkNotNull(
@@ -133,7 +132,7 @@
connectivityManager.registerDefaultNetworkCallback(networkCallback);
}
- @TargetApi(24)
+ @RequiresApi(24)
private void unregisterNetworkCallbackV24() {
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java
index 86e00e0..a10bd03 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java
@@ -19,6 +19,7 @@
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayList;
@@ -106,7 +107,7 @@
*/
protected final MediaSourceEventListener.EventDispatcher createEventDispatcher(
MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs) {
- Assertions.checkArgument(mediaPeriodId != null);
+ Assertions.checkNotNull(mediaPeriodId);
return eventDispatcher.withParameters(/* windowIndex= */ 0, mediaPeriodId, mediaTimeOffsetMs);
}
@@ -141,11 +142,21 @@
}
@Override
+ public final void addDrmEventListener(Handler handler, DrmSessionEventListener eventListener) {
+ eventDispatcher.addEventListener(handler, eventListener, DrmSessionEventListener.class);
+ }
+
+ @Override
+ public final void removeDrmEventListener(DrmSessionEventListener eventListener) {
+ eventDispatcher.removeEventListener(eventListener, DrmSessionEventListener.class);
+ }
+
+ @Override
public final void prepareSource(
MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) {
Looper looper = Looper.myLooper();
Assertions.checkArgument(this.looper == null || this.looper == looper);
- Timeline timeline = this.timeline;
+ @Nullable Timeline timeline = this.timeline;
mediaSourceCallers.add(caller);
if (this.looper == null) {
this.looper = looper;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java
new file mode 100644
index 0000000..f876458
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import android.net.Uri;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
+import com.google.android.exoplayer2.extractor.Extractor;
+import com.google.android.exoplayer2.extractor.ExtractorInput;
+import com.google.android.exoplayer2.extractor.ExtractorOutput;
+import com.google.android.exoplayer2.extractor.PositionHolder;
+import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
+import com.google.android.exoplayer2.upstream.DataReader;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * {@link ProgressiveMediaExtractor} built on top of {@link Extractor} instances, whose
+ * implementation classes are bundled in the app.
+ */
+/* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor {
+
+ private final Extractor[] extractors;
+
+ @Nullable private Extractor extractor;
+ @Nullable private ExtractorInput extractorInput;
+
+ /**
+ * Creates a holder that will select an extractor and initialize it using the specified output.
+ *
+ * @param extractors One or more extractors to choose from.
+ */
+ public BundledExtractorsAdapter(Extractor[] extractors) {
+ this.extractors = extractors;
+ }
+
+ @Override
+ public void init(
+ DataReader dataReader, Uri uri, long position, long length, ExtractorOutput output)
+ throws IOException {
+ extractorInput = new DefaultExtractorInput(dataReader, position, length);
+ if (extractor != null) {
+ return;
+ }
+ if (extractors.length == 1) {
+ this.extractor = extractors[0];
+ } else {
+ for (Extractor extractor : extractors) {
+ try {
+ if (extractor.sniff(extractorInput)) {
+ this.extractor = extractor;
+ break;
+ }
+ } catch (EOFException e) {
+ // Do nothing.
+ } finally {
+ extractorInput.resetPeekPosition();
+ }
+ }
+ if (extractor == null) {
+ throw new UnrecognizedInputFormatException(
+ "None of the available extractors ("
+ + Util.getCommaDelimitedSimpleClassNames(extractors)
+ + ") could read the stream.",
+ Assertions.checkNotNull(uri));
+ }
+ }
+ extractor.init(output);
+ }
+
+ @Override
+ public void release() {
+ if (extractor != null) {
+ extractor.release();
+ extractor = null;
+ }
+ extractorInput = null;
+ }
+
+ @Override
+ public void disableSeekingOnMp3Streams() {
+ if (extractor instanceof Mp3Extractor) {
+ ((Mp3Extractor) extractor).disableSeeking();
+ }
+ }
+
+ @Override
+ public long getCurrentInputPosition() {
+ return extractorInput != null ? extractorInput.getPosition() : C.POSITION_UNSET;
+ }
+
+ @Override
+ public void seek(long position, long seekTimeUs) {
+ Assertions.checkNotNull(extractor).seek(position, seekTimeUs);
+ }
+
+ @Override
+ public int read(PositionHolder positionHolder) throws IOException {
+ return Assertions.checkNotNull(extractor)
+ .read(Assertions.checkNotNull(extractorInput), positionHolder);
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java
index 8aafb9a..c5484a8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java
@@ -174,7 +174,7 @@
@Override
public long seekToUs(long positionUs) {
pendingInitialDiscontinuityPositionUs = C.TIME_UNSET;
- for (ClippingSampleStream sampleStream : sampleStreams) {
+ for (@Nullable ClippingSampleStream sampleStream : sampleStreams) {
if (sampleStream != null) {
sampleStream.clearSentEos();
}
@@ -310,21 +310,27 @@
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
- int result = childStream.readData(formatHolder, buffer, requireFormat);
+ @ReadDataResult int result = childStream.readData(formatHolder, buffer, requireFormat);
if (result == C.RESULT_FORMAT_READ) {
Format format = Assertions.checkNotNull(formatHolder.format);
if (format.encoderDelay != 0 || format.encoderPadding != 0) {
// Clear gapless playback metadata if the start/end points don't match the media.
int encoderDelay = startUs != 0 ? 0 : format.encoderDelay;
int encoderPadding = endUs != C.TIME_END_OF_SOURCE ? 0 : format.encoderPadding;
- formatHolder.format = format.copyWithGaplessInfo(encoderDelay, encoderPadding);
+ formatHolder.format =
+ format
+ .buildUpon()
+ .setEncoderDelay(encoderDelay)
+ .setEncoderPadding(encoderPadding)
+ .build();
}
return C.RESULT_FORMAT_READ;
}
if (endUs != C.TIME_END_OF_SOURCE
&& ((result == C.RESULT_BUFFER_READ && buffer.timeUs >= endUs)
|| (result == C.RESULT_NOTHING_READ
- && getBufferedPositionUs() == C.TIME_END_OF_SOURCE))) {
+ && getBufferedPositionUs() == C.TIME_END_OF_SOURCE
+ && !buffer.waitingForKeys))) {
buffer.clear();
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
sentEos = true;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
index 4780c07..d4ede3e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
@@ -319,14 +319,14 @@
}
Window window = timeline.getWindow(0, new Window());
startUs = Math.max(0, startUs);
+ if (!window.isPlaceholder && startUs != 0 && !window.isSeekable) {
+ throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
+ }
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs);
if (window.durationUs != C.TIME_UNSET) {
if (resolvedEndUs > window.durationUs) {
resolvedEndUs = window.durationUs;
}
- if (startUs != 0 && !window.isSeekable) {
- throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
- }
if (startUs > resolvedEndUs) {
throw new IllegalClippingException(IllegalClippingException.REASON_START_EXCEEDS_END);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java
index 424ee62..5cc75e8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java
@@ -19,8 +19,10 @@
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.UnknownNull;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.HashMap;
@@ -91,7 +93,7 @@
* @param timeline The timeline of the child source.
*/
protected abstract void onChildSourceInfoRefreshed(
- T id, MediaSource mediaSource, Timeline timeline);
+ @UnknownNull T id, MediaSource mediaSource, Timeline timeline);
/**
* Prepares a child source.
@@ -105,13 +107,14 @@
* @param id A unique id to identify the child source preparation. Null is allowed as an id.
* @param mediaSource The child {@link MediaSource}.
*/
- protected final void prepareChildSource(final T id, MediaSource mediaSource) {
+ protected final void prepareChildSource(@UnknownNull T id, MediaSource mediaSource) {
Assertions.checkArgument(!childSources.containsKey(id));
MediaSourceCaller caller =
(source, timeline) -> onChildSourceInfoRefreshed(id, source, timeline);
- MediaSourceEventListener eventListener = new ForwardingEventListener(id);
+ ForwardingEventListener eventListener = new ForwardingEventListener(id);
childSources.put(id, new MediaSourceAndListener(mediaSource, caller, eventListener));
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
+ mediaSource.addDrmEventListener(Assertions.checkNotNull(eventHandler), eventListener);
mediaSource.prepareSource(caller, mediaTransferListener);
if (!isEnabled()) {
mediaSource.disable(caller);
@@ -123,7 +126,7 @@
*
* @param id The unique id used to prepare the child source.
*/
- protected final void enableChildSource(final T id) {
+ protected final void enableChildSource(@UnknownNull T id) {
MediaSourceAndListener enabledChild = Assertions.checkNotNull(childSources.get(id));
enabledChild.mediaSource.enable(enabledChild.caller);
}
@@ -133,7 +136,7 @@
*
* @param id The unique id used to prepare the child source.
*/
- protected final void disableChildSource(final T id) {
+ protected final void disableChildSource(@UnknownNull T id) {
MediaSourceAndListener disabledChild = Assertions.checkNotNull(childSources.get(id));
disabledChild.mediaSource.disable(disabledChild.caller);
}
@@ -143,7 +146,7 @@
*
* @param id The unique id used to prepare the child source.
*/
- protected final void releaseChildSource(T id) {
+ protected final void releaseChildSource(@UnknownNull T id) {
MediaSourceAndListener removedChild = Assertions.checkNotNull(childSources.remove(id));
removedChild.mediaSource.releaseSource(removedChild.caller);
removedChild.mediaSource.removeEventListener(removedChild.eventListener);
@@ -157,7 +160,7 @@
* @param windowIndex A window index of the child source.
* @return The corresponding window index in the composite source.
*/
- protected int getWindowIndexForChildWindowIndex(T id, int windowIndex) {
+ protected int getWindowIndexForChildWindowIndex(@UnknownNull T id, int windowIndex) {
return windowIndex;
}
@@ -171,8 +174,9 @@
* @return The corresponding {@link MediaPeriodId} in the composite source. Null if no
* corresponding media period id can be determined.
*/
- protected @Nullable MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
- T id, MediaPeriodId mediaPeriodId) {
+ @Nullable
+ protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
+ @UnknownNull T id, MediaPeriodId mediaPeriodId) {
return mediaPeriodId;
}
@@ -184,7 +188,7 @@
* @param mediaTimeMs A media time of the child source, in milliseconds.
* @return The corresponding media time in the composite source, in milliseconds.
*/
- protected long getMediaTimeForChildMediaTime(@Nullable T id, long mediaTimeMs) {
+ protected long getMediaTimeForChildMediaTime(@UnknownNull T id, long mediaTimeMs) {
return mediaTimeMs;
}
@@ -214,16 +218,19 @@
}
}
- private final class ForwardingEventListener implements MediaSourceEventListener {
+ private final class ForwardingEventListener
+ implements MediaSourceEventListener, DrmSessionEventListener {
- private final T id;
+ @UnknownNull private final T id;
private EventDispatcher eventDispatcher;
- public ForwardingEventListener(T id) {
+ public ForwardingEventListener(@UnknownNull T id) {
this.eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
this.id = id;
}
+ // MediaSourceEventListener implementation
+
@Override
public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
@@ -314,10 +321,54 @@
}
}
+ // DrmSessionEventListener implementation
+
+ @Override
+ public void onDrmSessionAcquired() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionAcquired(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysLoaded() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmSessionManagerError(Exception error) {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionManagerError(error),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysRestored() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRestored(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmKeysRemoved() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysRemoved(),
+ DrmSessionEventListener.class);
+ }
+
+ @Override
+ public void onDrmSessionReleased() {
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmSessionReleased(),
+ DrmSessionEventListener.class);
+ }
+
/** Updates the event dispatcher and returns whether the event should be dispatched. */
private boolean maybeUpdateEventDispatcher(
int childWindowIndex, @Nullable MediaPeriodId childMediaPeriodId) {
- MediaPeriodId mediaPeriodId = null;
+ @Nullable MediaPeriodId mediaPeriodId = null;
if (childMediaPeriodId != null) {
mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId);
if (mediaPeriodId == null) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
index c1ab78a..8664c43 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
@@ -28,7 +28,6 @@
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -468,7 +467,7 @@
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(getChildPeriodUid(id.periodUid));
- MediaSourceHolder holder = mediaSourceByUid.get(mediaSourceHolderUid);
+ @Nullable MediaSourceHolder holder = mediaSourceByUid.get(mediaSourceHolderUid);
if (holder == null) {
// Stale event. The media source has already been removed.
holder = new MediaSourceHolder(new DummyMediaSource(), useLazyPreparation);
@@ -555,7 +554,7 @@
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
- Handler playbackThreadHandler = this.playbackThreadHandler;
+ @Nullable Handler playbackThreadHandler = this.playbackThreadHandler;
for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource);
}
@@ -565,6 +564,7 @@
}
mediaSourcesPublic.addAll(index, mediaSourceHolders);
if (playbackThreadHandler != null && !mediaSources.isEmpty()) {
+ @Nullable
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, callbackAction))
@@ -581,9 +581,10 @@
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
- Handler playbackThreadHandler = this.playbackThreadHandler;
+ @Nullable Handler playbackThreadHandler = this.playbackThreadHandler;
Util.removeRange(mediaSourcesPublic, fromIndex, toIndex);
if (playbackThreadHandler != null) {
+ @Nullable
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_REMOVE, new MessageData<>(fromIndex, toIndex, callbackAction))
@@ -600,9 +601,10 @@
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
- Handler playbackThreadHandler = this.playbackThreadHandler;
+ @Nullable Handler playbackThreadHandler = this.playbackThreadHandler;
mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex));
if (playbackThreadHandler != null) {
+ @Nullable
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_MOVE, new MessageData<>(currentIndex, newIndex, callbackAction))
@@ -616,7 +618,7 @@
private void setPublicShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Handler handler, @Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
- Handler playbackThreadHandler = this.playbackThreadHandler;
+ @Nullable Handler playbackThreadHandler = this.playbackThreadHandler;
if (playbackThreadHandler != null) {
int size = getSize();
if (shuffleOrder.getLength() != size) {
@@ -625,6 +627,7 @@
.cloneAndClear()
.cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
}
+ @Nullable
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(
@@ -772,9 +775,6 @@
}
private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) {
- if (mediaSourceHolder == null) {
- throw new IllegalArgumentException();
- }
if (mediaSourceHolder.childIndex + 1 < mediaSourceHolders.size()) {
MediaSourceHolder nextHolder = mediaSourceHolders.get(mediaSourceHolder.childIndex + 1);
int windowOffsetUpdate =
@@ -947,7 +947,7 @@
@Override
protected int getChildIndexByChildUid(Object childUid) {
- Integer index = childIndexByUid.get(childUid);
+ @Nullable Integer index = childIndexByUid.get(childUid);
return index == null ? C.INDEX_UNSET : index;
}
@@ -1002,7 +1002,7 @@
}
@Override
- public void maybeThrowSourceInfoRefreshError() throws IOException {
+ public void maybeThrowSourceInfoRefreshError() {
// Do nothing.
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
new file mode 100644
index 0000000..c3beb0d
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.SparseArray;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
+import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
+import com.google.android.exoplayer2.drm.MediaDrmCallback;
+import com.google.android.exoplayer2.offline.StreamKey;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The default {@link MediaSourceFactory} implementation.
+ *
+ * <p>This implementation delegates calls to {@link #createMediaSource(MediaItem)} to the following
+ * factories:
+ *
+ * <ul>
+ * <li>{@code DashMediaSource.Factory} if the item's {@link MediaItem.PlaybackProperties#sourceUri
+ * sourceUri} ends in '.mpd' or if its {@link MediaItem.PlaybackProperties#mimeType mimeType
+ * field} is explicitly set to {@link MimeTypes#APPLICATION_MPD} (Requires the <a
+ * href="https://exoplayer.dev/hello-world.html#add-exoplayer-modules">exoplayer-dash module
+ * to be added</a> to the app).
+ * <li>{@code HlsMediaSource.Factory} if the item's {@link MediaItem.PlaybackProperties#sourceUri
+ * sourceUri} ends in '.m3u8' or if its {@link MediaItem.PlaybackProperties#mimeType mimeType
+ * field} is explicitly set to {@link MimeTypes#APPLICATION_M3U8} (Requires the <a
+ * href="https://exoplayer.dev/hello-world.html#add-exoplayer-modules">exoplayer-hls module to
+ * be added</a> to the app).
+ * <li>{@code SsMediaSource.Factory} if the item's {@link MediaItem.PlaybackProperties#sourceUri
+ * sourceUri} ends in '.ism', '.ism/Manifest' or if its {@link
+ * MediaItem.PlaybackProperties#mimeType mimeType field} is explicitly set to {@link
+ * MimeTypes#APPLICATION_SS} (Requires the <a
+ * href="https://exoplayer.dev/hello-world.html#add-exoplayer-modules">
+ * exoplayer-smoothstreaming module to be added</a> to the app).
+ * <li>{@link ProgressiveMediaSource.Factory} serves as a fallback if the item's {@link
+ * MediaItem.PlaybackProperties#sourceUri sourceUri} doesn't match one of the above. It tries
+ * to infer the required extractor by using the {@link
+ * com.google.android.exoplayer2.extractor.DefaultExtractorsFactory}. An {@link
+ * UnrecognizedInputFormatException} is thrown if none of the available extractors can read
+ * the stream.
+ * </ul>
+ *
+ * <h3>DrmSessionManager creation for protected content</h3>
+ *
+ * <p>For a media item with a valid {@link
+ * com.google.android.exoplayer2.MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager} is
+ * created. The following setter can be used to optionally configure the creation:
+ *
+ * <ul>
+ * <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
+ * to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
+ * DefaultHttpDataSourceFactory}).
+ * </ul>
+ *
+ * <p>For media items without a drm configuration {@link DrmSessionManager#DUMMY} is used. To use an
+ * alternative dummy, apps can pass a drm session manager to {@link
+ * #setDrmSessionManager(DrmSessionManager)} which will be used for all items without a drm
+ * configuration.
+ */
+public final class DefaultMediaSourceFactory implements MediaSourceFactory {
+
+ /**
+ * Creates a new instance with the given {@link Context}.
+ *
+ * <p>This is functionally equivalent with calling {@code #newInstance(Context,
+ * DefaultDataSourceFactory)}.
+ *
+ * @param context The {@link Context}.
+ */
+ public static DefaultMediaSourceFactory newInstance(Context context) {
+ return newInstance(
+ context,
+ new DefaultDataSourceFactory(
+ context, Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY)));
+ }
+
+ /**
+ * Creates a new instance with the given {@link Context} and {@link DataSource.Factory}.
+ *
+ * @param context The {@link Context}.
+ * @param dataSourceFactory A {@link DataSource.Factory} to be used to create media sources.
+ */
+ public static DefaultMediaSourceFactory newInstance(
+ Context context, DataSource.Factory dataSourceFactory) {
+ return new DefaultMediaSourceFactory(context, dataSourceFactory);
+ }
+
+ private final DataSource.Factory dataSourceFactory;
+ private final SparseArray<MediaSourceFactory> mediaSourceFactories;
+ @C.ContentType private final int[] supportedTypes;
+ private final String userAgent;
+
+ private DrmSessionManager drmSessionManager;
+ private HttpDataSource.Factory drmHttpDataSourceFactory;
+ @Nullable private List<StreamKey> streamKeys;
+
+ private DefaultMediaSourceFactory(Context context, DataSource.Factory dataSourceFactory) {
+ this.dataSourceFactory = dataSourceFactory;
+ drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
+ userAgent = Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY);
+ drmHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
+ mediaSourceFactories = loadDelegates(dataSourceFactory);
+ supportedTypes = new int[mediaSourceFactories.size()];
+ for (int i = 0; i < mediaSourceFactories.size(); i++) {
+ supportedTypes[i] = mediaSourceFactories.keyAt(i);
+ }
+ }
+
+ /**
+ * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
+ * HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
+ * is passed the {@link DefaultHttpDataSourceFactory} is used.
+ *
+ * @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
+ * DefaultHttpDataSourceFactory}.
+ * @return This factory, for convenience.
+ */
+ public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ this.drmHttpDataSourceFactory =
+ drmHttpDataSourceFactory != null
+ ? drmHttpDataSourceFactory
+ : new DefaultHttpDataSourceFactory(userAgent);
+ return this;
+ }
+
+ @Override
+ public DefaultMediaSourceFactory setDrmSessionManager(
+ @Nullable DrmSessionManager drmSessionManager) {
+ this.drmSessionManager =
+ drmSessionManager != null
+ ? drmSessionManager
+ : DrmSessionManager.getDummyDrmSessionManager();
+ return this;
+ }
+
+ @Override
+ public DefaultMediaSourceFactory setLoadErrorHandlingPolicy(
+ @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
+ LoadErrorHandlingPolicy newLoadErrorHandlingPolicy =
+ loadErrorHandlingPolicy != null
+ ? loadErrorHandlingPolicy
+ : new DefaultLoadErrorHandlingPolicy();
+ for (int i = 0; i < mediaSourceFactories.size(); i++) {
+ mediaSourceFactories.valueAt(i).setLoadErrorHandlingPolicy(newLoadErrorHandlingPolicy);
+ }
+ return this;
+ }
+
+ /**
+ * @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
+ * #createMediaSource(MediaItem)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public DefaultMediaSourceFactory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
+ this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
+ return this;
+ }
+
+ @Override
+ public int[] getSupportedTypes() {
+ return Arrays.copyOf(supportedTypes, supportedTypes.length);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public MediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
+ @C.ContentType
+ int type =
+ inferContentType(
+ mediaItem.playbackProperties.sourceUri, mediaItem.playbackProperties.mimeType);
+ @Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
+ Assertions.checkNotNull(
+ mediaSourceFactory, "No suitable media source factory found for content type: " + type);
+ mediaSourceFactory.setDrmSessionManager(createDrmSessionManager(mediaItem));
+ mediaSourceFactory.setStreamKeys(
+ !mediaItem.playbackProperties.streamKeys.isEmpty()
+ ? mediaItem.playbackProperties.streamKeys
+ : streamKeys);
+
+ MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem);
+
+ List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
+ if (subtitles.isEmpty()) {
+ return maybeClipMediaSource(mediaItem, leafMediaSource);
+ }
+
+ MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
+ mediaSources[0] = leafMediaSource;
+ SingleSampleMediaSource.Factory singleSampleSourceFactory =
+ new SingleSampleMediaSource.Factory(dataSourceFactory);
+ for (int i = 0; i < subtitles.size(); i++) {
+ MediaItem.Subtitle subtitle = subtitles.get(i);
+ Format subtitleFormat =
+ new Format.Builder()
+ .setSampleMimeType(subtitle.mimeType)
+ .setLanguage(subtitle.language)
+ .setSelectionFlags(subtitle.selectionFlags)
+ .build();
+ mediaSources[i + 1] =
+ singleSampleSourceFactory.createMediaSource(
+ subtitle.uri, subtitleFormat, /* durationUs= */ C.TIME_UNSET);
+ }
+ return maybeClipMediaSource(mediaItem, new MergingMediaSource(mediaSources));
+ }
+
+ // internal methods
+
+ private DrmSessionManager createDrmSessionManager(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
+ if (mediaItem.playbackProperties.drmConfiguration == null
+ || mediaItem.playbackProperties.drmConfiguration.licenseUri == null
+ || Util.SDK_INT < 18) {
+ return drmSessionManager;
+ }
+ return new DefaultDrmSessionManager.Builder()
+ .setUuidAndExoMediaDrmProvider(
+ mediaItem.playbackProperties.drmConfiguration.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
+ .setMultiSession(mediaItem.playbackProperties.drmConfiguration.multiSession)
+ .setPlayClearSamplesWithoutKeys(
+ mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey)
+ .setUseDrmSessionsForClearContent(
+ Util.toArray(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes))
+ .build(createHttpMediaDrmCallback(mediaItem.playbackProperties.drmConfiguration));
+ }
+
+ private MediaDrmCallback createHttpMediaDrmCallback(MediaItem.DrmConfiguration drmConfiguration) {
+ Assertions.checkNotNull(drmConfiguration.licenseUri);
+ HttpMediaDrmCallback drmCallback =
+ new HttpMediaDrmCallback(drmConfiguration.licenseUri.toString(), drmHttpDataSourceFactory);
+ for (Map.Entry<String, String> entry : drmConfiguration.requestHeaders.entrySet()) {
+ drmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
+ }
+ return drmCallback;
+ }
+
+ private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
+ if (mediaItem.clippingProperties.startPositionMs == 0
+ && mediaItem.clippingProperties.endPositionMs == C.TIME_END_OF_SOURCE
+ && !mediaItem.clippingProperties.relativeToDefaultPosition) {
+ return mediaSource;
+ }
+ return new ClippingMediaSource(
+ mediaSource,
+ C.msToUs(mediaItem.clippingProperties.startPositionMs),
+ C.msToUs(mediaItem.clippingProperties.endPositionMs),
+ /* enableInitialDiscontinuity= */ !mediaItem.clippingProperties.startsAtKeyFrame,
+ /* allowDynamicClippingUpdates= */ mediaItem.clippingProperties.relativeToLiveWindow,
+ mediaItem.clippingProperties.relativeToDefaultPosition);
+ }
+
+ private static SparseArray<MediaSourceFactory> loadDelegates(
+ DataSource.Factory dataSourceFactory) {
+ SparseArray<MediaSourceFactory> factories = new SparseArray<>();
+ // LINT.IfChange
+ try {
+ Class<? extends MediaSourceFactory> factoryClazz =
+ Class.forName("com.google.android.exoplayer2.source.dash.DashMediaSource$Factory")
+ .asSubclass(MediaSourceFactory.class);
+ factories.put(
+ C.TYPE_DASH,
+ factoryClazz.getConstructor(DataSource.Factory.class).newInstance(dataSourceFactory));
+ } catch (Exception e) {
+ // Expected if the app was built without the dash module.
+ }
+ try {
+ Class<? extends MediaSourceFactory> factoryClazz =
+ Class.forName(
+ "com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory")
+ .asSubclass(MediaSourceFactory.class);
+ factories.put(
+ C.TYPE_SS,
+ factoryClazz.getConstructor(DataSource.Factory.class).newInstance(dataSourceFactory));
+ } catch (Exception e) {
+ // Expected if the app was built without the smoothstreaming module.
+ }
+ try {
+ Class<? extends MediaSourceFactory> factoryClazz =
+ Class.forName("com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory")
+ .asSubclass(MediaSourceFactory.class);
+ factories.put(
+ C.TYPE_HLS,
+ factoryClazz.getConstructor(DataSource.Factory.class).newInstance(dataSourceFactory));
+ } catch (Exception e) {
+ // Expected if the app was built without the hls module.
+ }
+ // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
+ factories.put(C.TYPE_OTHER, new ProgressiveMediaSource.Factory(dataSourceFactory));
+ return factories;
+ }
+
+ private static int inferContentType(Uri sourceUri, @Nullable String mimeType) {
+ if (mimeType == null) {
+ return Util.inferContentType(sourceUri);
+ }
+ switch (mimeType) {
+ case MimeTypes.APPLICATION_MPD:
+ return C.TYPE_DASH;
+ case MimeTypes.APPLICATION_M3U8:
+ return C.TYPE_HLS;
+ case MimeTypes.APPLICATION_SS:
+ return C.TYPE_SS;
+ default:
+ return Util.inferContentType(sourceUri);
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java
index 299b816..fe574cf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java
@@ -18,7 +18,6 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import java.io.IOException;
/**
* An empty {@link SampleStream}.
@@ -31,7 +30,7 @@
}
@Override
- public void maybeThrowError() throws IOException {
+ public void maybeThrowError() {
// Do nothing.
}
@@ -46,5 +45,4 @@
public int skipData(long positionUs) {
return 0;
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
index 97ee365..a4ed3ee 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
@@ -19,6 +19,7 @@
import android.os.Handler;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager;
@@ -111,13 +112,10 @@
}
/**
- * Sets a tag for the media source which will be published in the {@link
- * com.google.android.exoplayer2.Timeline} of the source as {@link
- * com.google.android.exoplayer2.Timeline.Window#tag}.
- *
- * @param tag A tag for the media source.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
@@ -138,6 +136,7 @@
* @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}.
* @return This factory, for convenience.
*/
+ @Override
public Factory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
this.loadErrorHandlingPolicy =
@@ -165,26 +164,36 @@
/** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmSessionManager} instead. */
@Deprecated
@Override
- public Factory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager) {
+ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
throw new UnsupportedOperationException();
}
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public ExtractorMediaSource createMediaSource(Uri uri) {
+ return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
+ }
+
/**
* Returns a new {@link ExtractorMediaSource} using the current parameters.
*
- * @param uri The {@link Uri}.
+ * @param mediaItem The {@link MediaItem}.
* @return The new {@link ExtractorMediaSource}.
+ * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
- public ExtractorMediaSource createMediaSource(Uri uri) {
+ public ExtractorMediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
return new ExtractorMediaSource(
- uri,
+ mediaItem.playbackProperties.sourceUri,
dataSourceFactory,
extractorsFactory,
loadErrorHandlingPolicy,
customCacheKey,
continueLoadingCheckIntervalBytes,
- tag);
+ mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/IcyDataSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/IcyDataSource.java
index d097073..84d2902 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/IcyDataSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/IcyDataSource.java
@@ -71,7 +71,7 @@
}
@Override
- public long open(DataSpec dataSpec) throws IOException {
+ public long open(DataSpec dataSpec) {
throw new UnsupportedOperationException();
}
@@ -91,8 +91,8 @@
return bytesRead;
}
- @Nullable
@Override
+ @Nullable
public Uri getUri() {
return upstream.getUri();
}
@@ -103,7 +103,7 @@
}
@Override
- public void close() throws IOException {
+ public void close() {
throw new UnsupportedOperationException();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoadEventInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoadEventInfo.java
index ae61cf4..cfef4ee 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoadEventInfo.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoadEventInfo.java
@@ -20,10 +20,19 @@
import com.google.android.exoplayer2.upstream.DataSpec;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/** {@link MediaSource} load event information. */
public final class LoadEventInfo {
+ /** Used for the generation of unique ids. */
+ private static final AtomicLong idSource = new AtomicLong();
+
+ /** Returns an non-negative identifier which is unique to the JVM instance. */
+ public static long getNewId() {
+ return idSource.getAndIncrement();
+ }
+
/** Defines the requested data. */
public final DataSpec dataSpec;
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java
index 8769a84..13f9758 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java
@@ -71,8 +71,8 @@
return maskingMediaSource.getTag();
}
- @Nullable
@Override
+ @Nullable
public Timeline getInitialTimeline() {
return loopCount != Integer.MAX_VALUE
? new LoopingTimeline(maskingMediaSource.getTimeline(), loopCount)
@@ -107,6 +107,7 @@
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
maskingMediaSource.releasePeriod(mediaPeriod);
+ @Nullable
MediaPeriodId childMediaPeriodId = mediaPeriodToChildMediaPeriodId.remove(mediaPeriod);
if (childMediaPeriodId != null) {
childMediaPeriodIdToMediaPeriodId.remove(childMediaPeriodId);
@@ -123,7 +124,8 @@
}
@Override
- protected @Nullable MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
+ @Nullable
+ protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
Void id, MediaPeriodId mediaPeriodId) {
return loopCount != Integer.MAX_VALUE
? childMediaPeriodIdToMediaPeriodId.get(mediaPeriodId)
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java
index 17ac6c0..142527a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java
@@ -91,8 +91,8 @@
}
/**
- * Overrides the default prepare position at which to prepare the media period. This value is only
- * used if called before {@link #createPeriod(MediaPeriodId)}.
+ * Overrides the default prepare position at which to prepare the media period. This method must
+ * be called before {@link #createPeriod(MediaPeriodId)}.
*
* @param preparePositionUs The default prepare position to use, in microseconds.
*/
@@ -100,6 +100,11 @@
preparePositionOverrideUs = preparePositionUs;
}
+ /** Returns the prepare position override set by {@link #overridePreparePositionUs(long)}. */
+ public long getPreparePositionOverrideUs() {
+ return preparePositionOverrideUs;
+ }
+
/**
* Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)} on the wrapped source
* then prepares it if {@link #prepare(Callback, long)} has been called. Call {@link
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java
index aff40d8..35b3e18 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java
@@ -26,7 +26,7 @@
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
-import java.io.IOException;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* A {@link MediaSource} that masks the {@link Timeline} with a placeholder until the actual media
@@ -59,7 +59,7 @@
this.useLazyPreparation = useLazyPreparation && mediaSource.isSingleWindow();
window = new Timeline.Window();
period = new Timeline.Period();
- Timeline initialTimeline = mediaSource.getInitialTimeline();
+ @Nullable Timeline initialTimeline = mediaSource.getInitialTimeline();
if (initialTimeline != null) {
timeline =
MaskingTimeline.createWithRealTimeline(
@@ -84,15 +84,15 @@
}
}
- @Nullable
@Override
+ @Nullable
public Object getTag() {
return mediaSource.getTag();
}
@Override
@SuppressWarnings("MissingSuperCall")
- public void maybeThrowSourceInfoRefreshError() throws IOException {
+ public void maybeThrowSourceInfoRefreshError() {
// Do nothing. Source info refresh errors will be thrown when calling
// MaskingMediaPeriod.maybeThrowPrepareError.
}
@@ -141,8 +141,14 @@
@Override
protected synchronized void onChildSourceInfoRefreshed(
Void id, MediaSource mediaSource, Timeline newTimeline) {
+ @Nullable MediaPeriodId idForMaskingPeriodPreparation = null;
if (isPrepared) {
timeline = timeline.cloneWithUpdatedTimeline(newTimeline);
+ if (unpreparedMaskingMediaPeriod != null) {
+ // Reset override in case the duration changed and we need to update our override.
+ setPreparePositionOverrideToUnpreparedMaskingPeriod(
+ unpreparedMaskingMediaPeriod.getPreparePositionOverrideUs());
+ }
} else if (newTimeline.isEmpty()) {
timeline =
hasRealTimeline
@@ -182,19 +188,22 @@
: MaskingTimeline.createWithRealTimeline(newTimeline, windowUid, periodUid);
if (unpreparedMaskingMediaPeriod != null) {
MaskingMediaPeriod maskingPeriod = unpreparedMaskingMediaPeriod;
- maskingPeriod.overridePreparePositionUs(periodPositionUs);
- MediaPeriodId idInSource =
+ setPreparePositionOverrideToUnpreparedMaskingPeriod(periodPositionUs);
+ idForMaskingPeriodPreparation =
maskingPeriod.id.copyWithPeriodUid(getInternalPeriodUid(maskingPeriod.id.periodUid));
- maskingPeriod.createPeriod(idInSource);
}
}
hasRealTimeline = true;
isPrepared = true;
refreshSourceInfo(this.timeline);
+ if (idForMaskingPeriodPreparation != null) {
+ Assertions.checkNotNull(unpreparedMaskingMediaPeriod)
+ .createPeriod(idForMaskingPeriodPreparation);
+ }
}
- @Nullable
@Override
+ @Nullable
protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
Void id, MediaPeriodId mediaPeriodId) {
return mediaPeriodId.copyWithPeriodUid(getExternalPeriodUid(mediaPeriodId.periodUid));
@@ -222,6 +231,27 @@
: internalPeriodUid;
}
+ @RequiresNonNull("unpreparedMaskingMediaPeriod")
+ private void setPreparePositionOverrideToUnpreparedMaskingPeriod(long preparePositionOverrideUs) {
+ MaskingMediaPeriod maskingPeriod = unpreparedMaskingMediaPeriod;
+ int maskingPeriodIndex = timeline.getIndexOfPeriod(maskingPeriod.id.periodUid);
+ if (maskingPeriodIndex == C.INDEX_UNSET) {
+ // The new timeline doesn't contain this period anymore. This can happen if the media source
+ // has multiple periods and removed the first period with a timeline update. Ignore the
+ // update, as the non-existing period will be released anyway as soon as the player receives
+ // this new timeline.
+ return;
+ }
+ long periodDurationUs = timeline.getPeriod(maskingPeriodIndex, period).durationUs;
+ if (periodDurationUs != C.TIME_UNSET) {
+ // Ensure the overridden position doesn't exceed the period duration.
+ if (preparePositionOverrideUs >= periodDurationUs) {
+ preparePositionOverrideUs = Math.max(0, periodDurationUs - 1);
+ }
+ }
+ maskingPeriod.overridePreparePositionUs(preparePositionOverrideUs);
+ }
+
/**
* Timeline used as placeholder for an unprepared media source. After preparation, a
* MaskingTimeline is used to keep the originally assigned dummy period ID.
@@ -331,7 +361,7 @@
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
- return window.set(
+ window.set(
Window.SINGLE_WINDOW_UID,
tag,
/* manifest= */ null,
@@ -347,6 +377,8 @@
/* firstPeriodIndex= */ 0,
/* lastPeriodIndex= */ 0,
/* positionInFirstPeriodUs= */ 0);
+ window.isPlaceholder = true;
+ return window;
}
@Override
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java
index f6dd4d7..479db2a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java
@@ -19,6 +19,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
@@ -229,6 +230,23 @@
void removeEventListener(MediaSourceEventListener eventListener);
/**
+ * Adds a {@link DrmSessionEventListener} to the list of listeners which are notified of DRM
+ * events for this media source.
+ *
+ * @param handler A handler on the which listener events will be posted.
+ * @param eventListener The listener to be added.
+ */
+ void addDrmEventListener(Handler handler, DrmSessionEventListener eventListener);
+
+ /**
+ * Removes a {@link DrmSessionEventListener} from the list of listeners which are notified of DRM
+ * events for this media source.
+ *
+ * @param eventListener The listener to be removed.
+ */
+ void removeDrmEventListener(DrmSessionEventListener eventListener);
+
+ /**
* Returns the initial dummy timeline that is returned immediately when the real timeline is not
* yet known, or null to let the player create an initial timeline.
*
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java
index 8123245..61bb55d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceEventListener.java
@@ -17,8 +17,6 @@
import android.net.Uri;
import android.os.Handler;
-import android.os.Looper;
-import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@@ -26,11 +24,12 @@
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.CopyOnWriteMultiset;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
/** Interface for callbacks to be notified of {@link MediaSource} events. */
public interface MediaSourceEventListener {
@@ -167,100 +166,69 @@
default void onDownstreamFormatChanged(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {}
- /** Dispatches events to {@link MediaSourceEventListener}s. */
- final class EventDispatcher {
+ /** @deprecated Use {@link MediaSourceEventDispatcher} directly instead. */
+ @Deprecated
+ final class EventDispatcher extends MediaSourceEventDispatcher {
- /** The timeline window index reported with the events. */
- public final int windowIndex;
- /** The {@link MediaPeriodId} reported with the events. */
- @Nullable public final MediaPeriodId mediaPeriodId;
-
- private final CopyOnWriteArrayList<ListenerAndHandler> listenerAndHandlers;
- private final long mediaTimeOffsetMs;
-
- /** Creates an event dispatcher. */
public EventDispatcher() {
- this(
- /* listenerAndHandlers= */ new CopyOnWriteArrayList<>(),
- /* windowIndex= */ 0,
- /* mediaPeriodId= */ null,
- /* mediaTimeOffsetMs= */ 0);
+ super();
}
private EventDispatcher(
- CopyOnWriteArrayList<ListenerAndHandler> listenerAndHandlers,
+ CopyOnWriteMultiset<ListenerInfo> listeners,
int windowIndex,
@Nullable MediaPeriodId mediaPeriodId,
long mediaTimeOffsetMs) {
- this.listenerAndHandlers = listenerAndHandlers;
- this.windowIndex = windowIndex;
- this.mediaPeriodId = mediaPeriodId;
- this.mediaTimeOffsetMs = mediaTimeOffsetMs;
+ super(listeners, windowIndex, mediaPeriodId, mediaTimeOffsetMs);
}
- /**
- * Creates a view of the event dispatcher with pre-configured window index, media period id, and
- * media time offset.
- *
- * @param windowIndex The timeline window index to be reported with the events.
- * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events.
- * @param mediaTimeOffsetMs The offset to be added to all media times, in milliseconds.
- * @return A view of the event dispatcher with the pre-configured parameters.
- */
- @CheckResult
+ @Override
public EventDispatcher withParameters(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs) {
- return new EventDispatcher(
- listenerAndHandlers, windowIndex, mediaPeriodId, mediaTimeOffsetMs);
+ return new EventDispatcher(listenerInfos, windowIndex, mediaPeriodId, mediaTimeOffsetMs);
}
/**
- * Adds a listener to the event dispatcher.
+ * Adds a {@link MediaSourceEventListener} to the event dispatcher.
+ *
+ * <p>This is equivalent to {@link #addEventListener(Handler, Object, Class)} with {@code
+ * listenerClass = MediaSourceEventListener.class} and is intended to ease the transition to
+ * using {@link MediaSourceEventDispatcher} everywhere.
*
* @param handler A handler on the which listener events will be posted.
* @param eventListener The listener to be added.
*/
public void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
- Assertions.checkArgument(handler != null && eventListener != null);
- listenerAndHandlers.add(new ListenerAndHandler(handler, eventListener));
+ addEventListener(handler, eventListener, MediaSourceEventListener.class);
}
/**
- * Removes a listener from the event dispatcher.
+ * Removes a {@link MediaSourceEventListener} from the event dispatcher.
+ *
+ * <p>This is equivalent to {@link #removeEventListener(Object, Class)} with {@code
+ * listenerClass = MediaSourceEventListener.class} and is intended to ease the transition to
+ * using {@link MediaSourceEventDispatcher} everywhere.
*
* @param eventListener The listener to be removed.
*/
public void removeEventListener(MediaSourceEventListener eventListener) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- if (listenerAndHandler.listener == eventListener) {
- listenerAndHandlers.remove(listenerAndHandler);
- }
- }
+ removeEventListener(eventListener, MediaSourceEventListener.class);
}
- /** Dispatches {@link #onMediaPeriodCreated(int, MediaPeriodId)}. */
public void mediaPeriodCreated() {
- MediaPeriodId mediaPeriodId = Assertions.checkNotNull(this.mediaPeriodId);
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onMediaPeriodCreated(windowIndex, mediaPeriodId));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onMediaPeriodCreated(windowIndex, Assertions.checkNotNull(mediaPeriodId)),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onMediaPeriodReleased(int, MediaPeriodId)}. */
public void mediaPeriodReleased() {
- MediaPeriodId mediaPeriodId = Assertions.checkNotNull(this.mediaPeriodId);
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onMediaPeriodReleased(windowIndex, mediaPeriodId));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onMediaPeriodReleased(windowIndex, Assertions.checkNotNull(mediaPeriodId)),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) {
loadStarted(
dataSpec,
@@ -274,7 +242,6 @@
elapsedRealtimeMs);
}
- /** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadStarted(
DataSpec dataSpec,
int dataType,
@@ -303,17 +270,13 @@
adjustMediaTime(mediaEndTimeUs)));
}
- /** Dispatches {@link #onLoadStarted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadStarted(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onLoadStarted(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onLoadStarted(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onLoadCompleted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCompleted(
DataSpec dataSpec,
Uri uri,
@@ -338,7 +301,6 @@
bytesLoaded);
}
- /** Dispatches {@link #onLoadCompleted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCompleted(
DataSpec dataSpec,
Uri uri,
@@ -366,18 +328,13 @@
adjustMediaTime(mediaEndTimeUs)));
}
- /** Dispatches {@link #onLoadCompleted(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCompleted(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () ->
- listener.onLoadCompleted(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onLoadCompleted(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onLoadCanceled(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCanceled(
DataSpec dataSpec,
Uri uri,
@@ -402,7 +359,6 @@
bytesLoaded);
}
- /** Dispatches {@link #onLoadCanceled(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCanceled(
DataSpec dataSpec,
Uri uri,
@@ -430,21 +386,13 @@
adjustMediaTime(mediaEndTimeUs)));
}
- /** Dispatches {@link #onLoadCanceled(int, MediaPeriodId, LoadEventInfo, MediaLoadData)}. */
public void loadCanceled(LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () ->
- listener.onLoadCanceled(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onLoadCanceled(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData),
+ MediaSourceEventListener.class);
}
- /**
- * Dispatches {@link #onLoadError(int, MediaPeriodId, LoadEventInfo, MediaLoadData, IOException,
- * boolean)}.
- */
public void loadError(
DataSpec dataSpec,
Uri uri,
@@ -473,10 +421,6 @@
wasCanceled);
}
- /**
- * Dispatches {@link #onLoadError(int, MediaPeriodId, LoadEventInfo, MediaLoadData, IOException,
- * boolean)}.
- */
public void loadError(
DataSpec dataSpec,
Uri uri,
@@ -508,37 +452,25 @@
wasCanceled);
}
- /**
- * Dispatches {@link #onLoadError(int, MediaPeriodId, LoadEventInfo, MediaLoadData, IOException,
- * boolean)}.
- */
public void loadError(
LoadEventInfo loadEventInfo,
MediaLoadData mediaLoadData,
IOException error,
boolean wasCanceled) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () ->
- listener.onLoadError(
- windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData, error, wasCanceled));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onLoadError(
+ windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData, error, wasCanceled),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onReadingStarted(int, MediaPeriodId)}. */
public void readingStarted() {
- MediaPeriodId mediaPeriodId = Assertions.checkNotNull(this.mediaPeriodId);
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onReadingStarted(windowIndex, mediaPeriodId));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onReadingStarted(windowIndex, Assertions.checkNotNull(mediaPeriodId)),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onUpstreamDiscarded(int, MediaPeriodId, MediaLoadData)}. */
public void upstreamDiscarded(int trackType, long mediaStartTimeUs, long mediaEndTimeUs) {
upstreamDiscarded(
new MediaLoadData(
@@ -551,18 +483,14 @@
adjustMediaTime(mediaEndTimeUs)));
}
- /** Dispatches {@link #onUpstreamDiscarded(int, MediaPeriodId, MediaLoadData)}. */
public void upstreamDiscarded(MediaLoadData mediaLoadData) {
- MediaPeriodId mediaPeriodId = Assertions.checkNotNull(this.mediaPeriodId);
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onUpstreamDiscarded(windowIndex, mediaPeriodId, mediaLoadData));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onUpstreamDiscarded(
+ windowIndex, Assertions.checkNotNull(mediaPeriodId), mediaLoadData),
+ MediaSourceEventListener.class);
}
- /** Dispatches {@link #onDownstreamFormatChanged(int, MediaPeriodId, MediaLoadData)}. */
public void downstreamFormatChanged(
int trackType,
@Nullable Format trackFormat,
@@ -580,38 +508,15 @@
/* mediaEndTimeMs= */ C.TIME_UNSET));
}
- /** Dispatches {@link #onDownstreamFormatChanged(int, MediaPeriodId, MediaLoadData)}. */
public void downstreamFormatChanged(MediaLoadData mediaLoadData) {
- for (ListenerAndHandler listenerAndHandler : listenerAndHandlers) {
- final MediaSourceEventListener listener = listenerAndHandler.listener;
- postOrRun(
- listenerAndHandler.handler,
- () -> listener.onDownstreamFormatChanged(windowIndex, mediaPeriodId, mediaLoadData));
- }
+ dispatch(
+ (listener, windowIndex, mediaPeriodId) ->
+ listener.onDownstreamFormatChanged(windowIndex, mediaPeriodId, mediaLoadData),
+ MediaSourceEventListener.class);
}
private long adjustMediaTime(long mediaTimeUs) {
- long mediaTimeMs = C.usToMs(mediaTimeUs);
- return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs;
- }
-
- private void postOrRun(Handler handler, Runnable runnable) {
- if (handler.getLooper() == Looper.myLooper()) {
- runnable.run();
- } else {
- handler.post(runnable);
- }
- }
-
- private static final class ListenerAndHandler {
-
- public final Handler handler;
- public final MediaSourceEventListener listener;
-
- public ListenerAndHandler(Handler handler, MediaSourceEventListener listener) {
- this.handler = handler;
- this.listener = listener;
- }
+ return adjustMediaTime(mediaTimeUs, mediaTimeOffsetMs);
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
index 11f1c5e..e1c52c0 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
@@ -18,20 +18,18 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.offline.StreamKey;
+import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import java.util.List;
/** Factory for creating {@link MediaSource}s from URIs. */
public interface MediaSourceFactory {
- /**
- * Sets a list of {@link StreamKey StreamKeys} by which the manifest is filtered.
- *
- * @param streamKeys A list of {@link StreamKey StreamKeys}.
- * @return This factory, for convenience.
- */
+ /** @deprecated Use {@link MediaItem.PlaybackProperties#streamKeys} instead. */
+ @Deprecated
default MediaSourceFactory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
return this;
}
@@ -42,15 +40,16 @@
* @param drmSessionManager The {@link DrmSessionManager}.
* @return This factory, for convenience.
*/
- MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager);
+ MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
/**
- * Creates a new {@link MediaSource} with the specified {@code uri}.
+ * Sets an optional {@link LoadErrorHandlingPolicy}.
*
- * @param uri The URI to play.
- * @return The new {@link MediaSource media source}.
+ * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}.
+ * @return This factory, for convenience.
*/
- MediaSource createMediaSource(Uri uri);
+ MediaSourceFactory setLoadErrorHandlingPolicy(
+ @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy);
/**
* Returns the {@link C.ContentType content types} supported by media sources created by this
@@ -58,4 +57,18 @@
*/
@C.ContentType
int[] getSupportedTypes();
+
+ /**
+ * Creates a new {@link MediaSource} with the specified {@link MediaItem}.
+ *
+ * @param mediaItem The media item to play.
+ * @return The new {@link MediaSource media source}.
+ */
+ MediaSource createMediaSource(MediaItem mediaItem);
+
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @Deprecated
+ default MediaSource createMediaSource(Uri uri) {
+ return createMediaSource(MediaItem.fromUri(uri));
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
index afa25d6..c2e0c47 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
@@ -17,22 +17,26 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.SeekParameters;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Merges multiple {@link MediaPeriod}s.
*/
/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
- public final MediaPeriod[] periods;
-
+ private final MediaPeriod[] periods;
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final ArrayList<MediaPeriod> childrenPendingPreparation;
@@ -42,7 +46,9 @@
private MediaPeriod[] enabledPeriods;
private SequenceableLoader compositeSequenceableLoader;
- public MergingMediaPeriod(CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
+ public MergingMediaPeriod(
+ CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
+ long[] periodTimeOffsetsUs,
MediaPeriod... periods) {
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.periods = periods;
@@ -51,6 +57,22 @@
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader();
streamPeriodIndices = new IdentityHashMap<>();
enabledPeriods = new MediaPeriod[0];
+ for (int i = 0; i < periods.length; i++) {
+ if (periodTimeOffsetsUs[i] != 0) {
+ this.periods[i] = new TimeOffsetMediaPeriod(periods[i], periodTimeOffsetsUs[i]);
+ }
+ }
+ }
+
+ /**
+ * Returns the child period passed to {@link
+ * #MergingMediaPeriod(CompositeSequenceableLoaderFactory, long[], MediaPeriod...)} at the
+ * specified index.
+ */
+ public MediaPeriod getChildPeriod(int index) {
+ return periods[index] instanceof TimeOffsetMediaPeriod
+ ? ((TimeOffsetMediaPeriod) periods[index]).mediaPeriod
+ : periods[index];
}
@Override
@@ -181,23 +203,32 @@
@Override
public long readDiscontinuity() {
- long positionUs = periods[0].readDiscontinuity();
- // Periods other than the first one are not allowed to report discontinuities.
- for (int i = 1; i < periods.length; i++) {
- if (periods[i].readDiscontinuity() != C.TIME_UNSET) {
- throw new IllegalStateException("Child reported discontinuity.");
- }
- }
- // It must be possible to seek enabled periods to the new position, if there is one.
- if (positionUs != C.TIME_UNSET) {
- for (MediaPeriod enabledPeriod : enabledPeriods) {
- if (enabledPeriod != periods[0]
- && enabledPeriod.seekToUs(positionUs) != positionUs) {
+ long discontinuityUs = C.TIME_UNSET;
+ for (MediaPeriod period : enabledPeriods) {
+ long otherDiscontinuityUs = period.readDiscontinuity();
+ if (otherDiscontinuityUs != C.TIME_UNSET) {
+ if (discontinuityUs == C.TIME_UNSET) {
+ discontinuityUs = otherDiscontinuityUs;
+ // First reported discontinuity. Seek all previous periods to the new position.
+ for (MediaPeriod previousPeriod : enabledPeriods) {
+ if (previousPeriod == period) {
+ break;
+ }
+ if (previousPeriod.seekToUs(discontinuityUs) != discontinuityUs) {
+ throw new IllegalStateException("Unexpected child seekToUs result.");
+ }
+ }
+ } else if (otherDiscontinuityUs != discontinuityUs) {
+ throw new IllegalStateException("Conflicting discontinuities.");
+ }
+ } else if (discontinuityUs != C.TIME_UNSET) {
+ // We already have a discontinuity, seek this period to the new position.
+ if (period.seekToUs(discontinuityUs) != discontinuityUs) {
throw new IllegalStateException("Unexpected child seekToUs result.");
}
}
}
- return positionUs;
+ return discontinuityUs;
}
@Override
@@ -253,4 +284,173 @@
Assertions.checkNotNull(callback).onContinueLoadingRequested(this);
}
+ private static final class TimeOffsetMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
+
+ private final MediaPeriod mediaPeriod;
+ private final long timeOffsetUs;
+
+ private @MonotonicNonNull Callback callback;
+
+ public TimeOffsetMediaPeriod(MediaPeriod mediaPeriod, long timeOffsetUs) {
+ this.mediaPeriod = mediaPeriod;
+ this.timeOffsetUs = timeOffsetUs;
+ }
+
+ @Override
+ public void prepare(Callback callback, long positionUs) {
+ this.callback = callback;
+ mediaPeriod.prepare(/* callback= */ this, positionUs - timeOffsetUs);
+ }
+
+ @Override
+ public void maybeThrowPrepareError() throws IOException {
+ mediaPeriod.maybeThrowPrepareError();
+ }
+
+ @Override
+ public TrackGroupArray getTrackGroups() {
+ return mediaPeriod.getTrackGroups();
+ }
+
+ @Override
+ public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
+ return mediaPeriod.getStreamKeys(trackSelections);
+ }
+
+ @Override
+ public long selectTracks(
+ @NullableType TrackSelection[] selections,
+ boolean[] mayRetainStreamFlags,
+ @NullableType SampleStream[] streams,
+ boolean[] streamResetFlags,
+ long positionUs) {
+ @NullableType SampleStream[] childStreams = new SampleStream[streams.length];
+ for (int i = 0; i < streams.length; i++) {
+ TimeOffsetSampleStream sampleStream = (TimeOffsetSampleStream) streams[i];
+ childStreams[i] = sampleStream != null ? sampleStream.getChildStream() : null;
+ }
+ long startPositionUs =
+ mediaPeriod.selectTracks(
+ selections,
+ mayRetainStreamFlags,
+ childStreams,
+ streamResetFlags,
+ positionUs - timeOffsetUs);
+ for (int i = 0; i < streams.length; i++) {
+ @Nullable SampleStream childStream = childStreams[i];
+ if (childStream == null) {
+ streams[i] = null;
+ } else if (streams[i] == null
+ || ((TimeOffsetSampleStream) streams[i]).getChildStream() != childStream) {
+ streams[i] = new TimeOffsetSampleStream(childStream, timeOffsetUs);
+ }
+ }
+ return startPositionUs + timeOffsetUs;
+ }
+
+ @Override
+ public void discardBuffer(long positionUs, boolean toKeyframe) {
+ mediaPeriod.discardBuffer(positionUs - timeOffsetUs, toKeyframe);
+ }
+
+ @Override
+ public long readDiscontinuity() {
+ long discontinuityPositionUs = mediaPeriod.readDiscontinuity();
+ return discontinuityPositionUs == C.TIME_UNSET
+ ? C.TIME_UNSET
+ : discontinuityPositionUs + timeOffsetUs;
+ }
+
+ @Override
+ public long seekToUs(long positionUs) {
+ return mediaPeriod.seekToUs(positionUs - timeOffsetUs) + timeOffsetUs;
+ }
+
+ @Override
+ public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
+ return mediaPeriod.getAdjustedSeekPositionUs(positionUs - timeOffsetUs, seekParameters)
+ + timeOffsetUs;
+ }
+
+ @Override
+ public long getBufferedPositionUs() {
+ long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
+ return bufferedPositionUs == C.TIME_END_OF_SOURCE
+ ? C.TIME_END_OF_SOURCE
+ : bufferedPositionUs + timeOffsetUs;
+ }
+
+ @Override
+ public long getNextLoadPositionUs() {
+ long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs();
+ return nextLoadPositionUs == C.TIME_END_OF_SOURCE
+ ? C.TIME_END_OF_SOURCE
+ : nextLoadPositionUs + timeOffsetUs;
+ }
+
+ @Override
+ public boolean continueLoading(long positionUs) {
+ return mediaPeriod.continueLoading(positionUs - timeOffsetUs);
+ }
+
+ @Override
+ public boolean isLoading() {
+ return mediaPeriod.isLoading();
+ }
+
+ @Override
+ public void reevaluateBuffer(long positionUs) {
+ mediaPeriod.reevaluateBuffer(positionUs - timeOffsetUs);
+ }
+
+ @Override
+ public void onPrepared(MediaPeriod mediaPeriod) {
+ Assertions.checkNotNull(callback).onPrepared(/* mediaPeriod= */ this);
+ }
+
+ @Override
+ public void onContinueLoadingRequested(MediaPeriod source) {
+ Assertions.checkNotNull(callback).onContinueLoadingRequested(/* source= */ this);
+ }
+ }
+
+ private static final class TimeOffsetSampleStream implements SampleStream {
+
+ private final SampleStream sampleStream;
+ private final long timeOffsetUs;
+
+ public TimeOffsetSampleStream(SampleStream sampleStream, long timeOffsetUs) {
+ this.sampleStream = sampleStream;
+ this.timeOffsetUs = timeOffsetUs;
+ }
+
+ public SampleStream getChildStream() {
+ return sampleStream;
+ }
+
+ @Override
+ public boolean isReady() {
+ return sampleStream.isReady();
+ }
+
+ @Override
+ public void maybeThrowError() throws IOException {
+ sampleStream.maybeThrowError();
+ }
+
+ @Override
+ public int readData(
+ FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
+ int readResult = sampleStream.readData(formatHolder, buffer, formatRequired);
+ if (readResult == C.RESULT_BUFFER_READ) {
+ buffer.timeUs = Math.max(0, buffer.timeUs + timeOffsetUs);
+ }
+ return readResult;
+ }
+
+ @Override
+ public int skipData(long positionUs) {
+ return sampleStream.skipData(positionUs - timeOffsetUs);
+ }
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
index dd7675f..d69c037 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
@@ -66,34 +66,59 @@
private static final int PERIOD_COUNT_UNSET = -1;
+ private final boolean adjustPeriodTimeOffsets;
private final MediaSource[] mediaSources;
private final Timeline[] timelines;
private final ArrayList<MediaSource> pendingTimelineSources;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private int periodCount;
+ private long[][] periodTimeOffsetsUs;
@Nullable private IllegalMergeException mergeError;
/**
+ * Creates a merging media source.
+ *
+ * <p>Offsets between the timestamps in the media sources will not be adjusted.
+ *
* @param mediaSources The {@link MediaSource}s to merge.
*/
public MergingMediaSource(MediaSource... mediaSources) {
- this(new DefaultCompositeSequenceableLoaderFactory(), mediaSources);
+ this(/* adjustPeriodTimeOffsets= */ false, mediaSources);
}
/**
- * @param compositeSequenceableLoaderFactory A factory to create composite
- * {@link SequenceableLoader}s for when this media source loads data from multiple streams
- * (video, audio etc...).
+ * Creates a merging media source.
+ *
+ * @param adjustPeriodTimeOffsets Whether to adjust timestamps of the merged media sources to all
+ * start at the same time.
* @param mediaSources The {@link MediaSource}s to merge.
*/
- public MergingMediaSource(CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
+ public MergingMediaSource(boolean adjustPeriodTimeOffsets, MediaSource... mediaSources) {
+ this(adjustPeriodTimeOffsets, new DefaultCompositeSequenceableLoaderFactory(), mediaSources);
+ }
+
+ /**
+ * Creates a merging media source.
+ *
+ * @param adjustPeriodTimeOffsets Whether to adjust timestamps of the merged media sources to all
+ * start at the same time.
+ * @param compositeSequenceableLoaderFactory A factory to create composite {@link
+ * SequenceableLoader}s for when this media source loads data from multiple streams (video,
+ * audio etc...).
+ * @param mediaSources The {@link MediaSource}s to merge.
+ */
+ public MergingMediaSource(
+ boolean adjustPeriodTimeOffsets,
+ CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
MediaSource... mediaSources) {
+ this.adjustPeriodTimeOffsets = adjustPeriodTimeOffsets;
this.mediaSources = mediaSources;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
pendingTimelineSources = new ArrayList<>(Arrays.asList(mediaSources));
periodCount = PERIOD_COUNT_UNSET;
timelines = new Timeline[mediaSources.length];
+ periodTimeOffsetsUs = new long[0][];
}
@Override
@@ -125,16 +150,19 @@
for (int i = 0; i < periods.length; i++) {
MediaPeriodId childMediaPeriodId =
id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex));
- periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator, startPositionUs);
+ periods[i] =
+ mediaSources[i].createPeriod(
+ childMediaPeriodId, allocator, startPositionUs - periodTimeOffsetsUs[periodIndex][i]);
}
- return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods);
+ return new MergingMediaPeriod(
+ compositeSequenceableLoaderFactory, periodTimeOffsetsUs[periodIndex], periods);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
MergingMediaPeriod mergingPeriod = (MergingMediaPeriod) mediaPeriod;
for (int i = 0; i < mediaSources.length; i++) {
- mediaSources[i].releasePeriod(mergingPeriod.periods[i]);
+ mediaSources[i].releasePeriod(mergingPeriod.getChildPeriod(i));
}
}
@@ -151,15 +179,24 @@
@Override
protected void onChildSourceInfoRefreshed(
Integer id, MediaSource mediaSource, Timeline timeline) {
- if (mergeError == null) {
- mergeError = checkTimelineMerges(timeline);
- }
if (mergeError != null) {
return;
}
+ if (periodCount == PERIOD_COUNT_UNSET) {
+ periodCount = timeline.getPeriodCount();
+ } else if (timeline.getPeriodCount() != periodCount) {
+ mergeError = new IllegalMergeException(IllegalMergeException.REASON_PERIOD_COUNT_MISMATCH);
+ return;
+ }
+ if (periodTimeOffsetsUs.length == 0) {
+ periodTimeOffsetsUs = new long[periodCount][timelines.length];
+ }
pendingTimelineSources.remove(mediaSource);
timelines[id] = timeline;
if (pendingTimelineSources.isEmpty()) {
+ if (adjustPeriodTimeOffsets) {
+ computePeriodTimeOffsets();
+ }
refreshSourceInfo(timelines[0]);
}
}
@@ -171,14 +208,17 @@
return id == 0 ? mediaPeriodId : null;
}
- @Nullable
- private IllegalMergeException checkTimelineMerges(Timeline timeline) {
- if (periodCount == PERIOD_COUNT_UNSET) {
- periodCount = timeline.getPeriodCount();
- } else if (timeline.getPeriodCount() != periodCount) {
- return new IllegalMergeException(IllegalMergeException.REASON_PERIOD_COUNT_MISMATCH);
+ private void computePeriodTimeOffsets() {
+ Timeline.Period period = new Timeline.Period();
+ for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
+ long primaryWindowOffsetUs =
+ -timelines[0].getPeriod(periodIndex, period).getPositionInWindowUs();
+ for (int timelineIndex = 1; timelineIndex < timelines.length; timelineIndex++) {
+ long secondaryWindowOffsetUs =
+ -timelines[timelineIndex].getPeriod(periodIndex, period).getPositionInWindowUs();
+ periodTimeOffsetsUs[periodIndex][timelineIndex] =
+ primaryWindowOffsetUs - secondaryWindowOffsetUs;
+ }
}
- return null;
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java
new file mode 100644
index 0000000..6cc7c91
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import android.net.Uri;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.Extractor;
+import com.google.android.exoplayer2.extractor.ExtractorOutput;
+import com.google.android.exoplayer2.extractor.PositionHolder;
+import com.google.android.exoplayer2.upstream.DataReader;
+import java.io.IOException;
+
+/** Extracts the contents of a container file from a progressive media stream. */
+/* package */ interface ProgressiveMediaExtractor {
+
+ /**
+ * Initializes the underlying infrastructure for reading from the input.
+ *
+ * @param dataReader The {@link DataReader} from which data should be read.
+ * @param uri The {@link Uri} from which the media is obtained.
+ * @param position The initial position of the {@code dataReader} in the stream.
+ * @param length The length of the stream, or {@link C#LENGTH_UNSET} if length is unknown.
+ * @param output The {@link ExtractorOutput} that will be used to initialize the selected
+ * extractor.
+ * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected.
+ * @throws IOException Thrown if the input could not be read.
+ */
+ void init(DataReader dataReader, Uri uri, long position, long length, ExtractorOutput output)
+ throws IOException;
+
+ /** Releases any held resources. */
+ void release();
+
+ /**
+ * Disables seeking in MP3 streams.
+ *
+ * <p>MP3 live streams commonly have seekable metadata, despite being unseekable.
+ */
+ void disableSeekingOnMp3Streams();
+
+ /**
+ * Returns the current read position in the input stream, or {@link C#POSITION_UNSET} if no input
+ * is available.
+ */
+ long getCurrentInputPosition();
+
+ /**
+ * Notifies the extracting infrastructure that a seek has occurred.
+ *
+ * @param position The byte offset in the stream from which data will be provided.
+ * @param seekTimeUs The seek time in microseconds.
+ */
+ void seek(long position, long seekTimeUs);
+
+ /**
+ * Extracts data starting at the current input stream position.
+ *
+ * @param positionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated to
+ * hold the position of the required data.
+ * @return One of the {@link Extractor}{@code .RESULT_*} values.
+ * @throws IOException If an error occurred reading from the input.
+ */
+ int read(PositionHolder positionHolder) throws IOException;
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
index 688c30a..2cbfeb8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
@@ -25,16 +25,13 @@
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
import com.google.android.exoplayer2.extractor.SeekMap.Unseekable;
import com.google.android.exoplayer2.extractor.TrackOutput;
-import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
@@ -53,13 +50,15 @@
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
-import java.io.EOFException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link MediaPeriod} that extracts data using an {@link Extractor}. */
/* package */ final class ProgressiveMediaPeriod
@@ -94,11 +93,11 @@
private static final Map<String, String> ICY_METADATA_HEADERS = createIcyMetadataHeaders();
private static final Format ICY_FORMAT =
- Format.createSampleFormat("icy", MimeTypes.APPLICATION_ICY, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setId("icy").setSampleMimeType(MimeTypes.APPLICATION_ICY).build();
private final Uri uri;
private final DataSource dataSource;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final EventDispatcher eventDispatcher;
private final Listener listener;
@@ -106,31 +105,31 @@
@Nullable private final String customCacheKey;
private final long continueLoadingCheckIntervalBytes;
private final Loader loader;
- private final ExtractorHolder extractorHolder;
+ private final ProgressiveMediaExtractor progressiveMediaExtractor;
private final ConditionVariable loadCondition;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
@Nullable private Callback callback;
- @Nullable private SeekMap seekMap;
@Nullable private IcyHeaders icyHeaders;
private SampleQueue[] sampleQueues;
private TrackId[] sampleQueueTrackIds;
private boolean sampleQueuesBuilt;
- private boolean prepared;
- @Nullable private PreparedState preparedState;
+ private boolean prepared;
private boolean haveAudioVideoTracks;
+ private @MonotonicNonNull TrackState trackState;
+ private @MonotonicNonNull SeekMap seekMap;
+ private long durationUs;
+ private boolean isLive;
private int dataType;
private boolean seenFirstTrackSelection;
private boolean notifyDiscontinuity;
private boolean notifiedReadingStarted;
private int enabledTrackCount;
- private long durationUs;
private long length;
- private boolean isLive;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
@@ -162,7 +161,7 @@
Uri uri,
DataSource dataSource,
Extractor[] extractors,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
Listener listener,
@@ -179,7 +178,7 @@
this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("Loader:ProgressiveMediaPeriod");
- extractorHolder = new ExtractorHolder(extractors);
+ progressiveMediaExtractor = new BundledExtractorsAdapter(extractors);
loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare;
onContinueLoadingRequestedRunnable =
@@ -219,7 +218,7 @@
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.release();
}
- extractorHolder.release();
+ progressiveMediaExtractor.release();
}
@Override
@@ -239,7 +238,8 @@
@Override
public TrackGroupArray getTrackGroups() {
- return getPreparedState().tracks;
+ assertPrepared();
+ return trackState.tracks;
}
@Override
@@ -249,9 +249,9 @@
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs) {
- PreparedState preparedState = getPreparedState();
- TrackGroupArray tracks = preparedState.tracks;
- boolean[] trackEnabledStates = preparedState.trackEnabledStates;
+ assertPrepared();
+ TrackGroupArray tracks = trackState.tracks;
+ boolean[] trackEnabledStates = trackState.trackEnabledStates;
int oldEnabledTrackCount = enabledTrackCount;
// Deselect old tracks.
for (int i = 0; i < selections.length; i++) {
@@ -320,10 +320,11 @@
@Override
public void discardBuffer(long positionUs, boolean toKeyframe) {
+ assertPrepared();
if (isPendingReset()) {
return;
}
- boolean[] trackEnabledStates = getPreparedState().trackEnabledStates;
+ boolean[] trackEnabledStates = trackState.trackEnabledStates;
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
sampleQueues[i].discardTo(positionUs, toKeyframe, trackEnabledStates[i]);
@@ -377,7 +378,8 @@
@Override
public long getBufferedPositionUs() {
- boolean[] trackIsAudioVideoFlags = getPreparedState().trackIsAudioVideoFlags;
+ assertPrepared();
+ boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
if (loadingFinished) {
return C.TIME_END_OF_SOURCE;
} else if (isPendingReset()) {
@@ -403,9 +405,8 @@
@Override
public long seekToUs(long positionUs) {
- PreparedState preparedState = getPreparedState();
- SeekMap seekMap = preparedState.seekMap;
- boolean[] trackIsAudioVideoFlags = preparedState.trackIsAudioVideoFlags;
+ assertPrepared();
+ boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
// Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0;
@@ -440,7 +441,7 @@
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
- SeekMap seekMap = getPreparedState().seekMap;
+ assertPrepared();
if (!seekMap.isSeekable()) {
// Treat all seeks into non-seekable media as being to t=0.
return 0;
@@ -502,10 +503,10 @@
}
private void maybeNotifyDownstreamFormat(int track) {
- PreparedState preparedState = getPreparedState();
- boolean[] trackNotifiedDownstreamFormats = preparedState.trackNotifiedDownstreamFormats;
+ assertPrepared();
+ boolean[] trackNotifiedDownstreamFormats = trackState.trackNotifiedDownstreamFormats;
if (!trackNotifiedDownstreamFormats[track]) {
- Format trackFormat = preparedState.tracks.get(track).getFormat(/* index= */ 0);
+ Format trackFormat = trackState.tracks.get(track).getFormat(/* index= */ 0);
eventDispatcher.downstreamFormatChanged(
MimeTypes.getTrackType(trackFormat.sampleMimeType),
trackFormat,
@@ -517,7 +518,8 @@
}
private void maybeStartDeferredRetry(int track) {
- boolean[] trackIsAudioVideoFlags = getPreparedState().trackIsAudioVideoFlags;
+ assertPrepared();
+ boolean[] trackIsAudioVideoFlags = trackState.trackIsAudioVideoFlags;
if (!pendingDeferredRetry
|| !trackIsAudioVideoFlags[track]
|| sampleQueues[track].isReady(/* loadingFinished= */ false)) {
@@ -541,8 +543,8 @@
// Loader.Callback implementation.
@Override
- public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs,
- long loadDurationMs) {
+ public void onLoadCompleted(
+ ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
if (durationUs == C.TIME_UNSET && seekMap != null) {
boolean isSeekable = seekMap.isSeekable();
long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
@@ -570,8 +572,8 @@
}
@Override
- public void onLoadCanceled(ExtractingLoadable loadable, long elapsedRealtimeMs,
- long loadDurationMs, boolean released) {
+ public void onLoadCanceled(
+ ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
eventDispatcher.loadCanceled(
loadable.dataSpec,
loadable.dataSource.getLastOpenedUri(),
@@ -653,8 +655,7 @@
@Override
public void seekMap(SeekMap seekMap) {
- this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs */ C.TIME_UNSET);
- handler.post(maybeFinishPrepareRunnable);
+ handler.post(() -> setSeekMap(seekMap));
}
// Icy metadata. Called by the loading thread.
@@ -679,7 +680,7 @@
return sampleQueues[i];
}
}
- SampleQueue trackOutput = new SampleQueue(allocator, drmSessionManager);
+ SampleQueue trackOutput = new SampleQueue(allocator, drmSessionManager, eventDispatcher);
trackOutput.setUpstreamFormatChangeListener(this);
@NullableType
TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
@@ -691,8 +692,18 @@
return trackOutput;
}
+ private void setSeekMap(SeekMap seekMap) {
+ this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs= */ C.TIME_UNSET);
+ if (!prepared) {
+ maybeFinishPrepare();
+ }
+ durationUs = seekMap.getDurationUs();
+ isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
+ dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
+ listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
+ }
+
private void maybeFinishPrepare() {
- SeekMap seekMap = this.seekMap;
if (released || prepared || !sampleQueuesBuilt || seekMap == null) {
return;
}
@@ -705,45 +716,40 @@
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
boolean[] trackIsAudioVideoFlags = new boolean[trackCount];
- durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) {
- Format trackFormat = sampleQueues[i].getUpstreamFormat();
- String mimeType = trackFormat.sampleMimeType;
+ Format trackFormat = Assertions.checkNotNull(sampleQueues[i].getUpstreamFormat());
+ @Nullable String mimeType = trackFormat.sampleMimeType;
boolean isAudio = MimeTypes.isAudio(mimeType);
boolean isAudioVideo = isAudio || MimeTypes.isVideo(mimeType);
trackIsAudioVideoFlags[i] = isAudioVideo;
haveAudioVideoTracks |= isAudioVideo;
- IcyHeaders icyHeaders = this.icyHeaders;
+ @Nullable IcyHeaders icyHeaders = this.icyHeaders;
if (icyHeaders != null) {
if (isAudio || sampleQueueTrackIds[i].isIcyTrack) {
- Metadata metadata = trackFormat.metadata;
- trackFormat =
- trackFormat.copyWithMetadata(
- metadata == null
- ? new Metadata(icyHeaders)
- : metadata.copyWithAppendedEntries(icyHeaders));
+ @Nullable Metadata metadata = trackFormat.metadata;
+ if (metadata == null) {
+ metadata = new Metadata(icyHeaders);
+ } else {
+ metadata = metadata.copyWithAppendedEntries(icyHeaders);
+ }
+ trackFormat = trackFormat.buildUpon().setMetadata(metadata).build();
}
+ // Update the track format with the bitrate from the ICY header only if it declares neither
+ // an average or peak bitrate of its own.
if (isAudio
- && trackFormat.bitrate == Format.NO_VALUE
+ && trackFormat.averageBitrate == Format.NO_VALUE
+ && trackFormat.peakBitrate == Format.NO_VALUE
&& icyHeaders.bitrate != Format.NO_VALUE) {
- trackFormat = trackFormat.copyWithBitrate(icyHeaders.bitrate);
+ trackFormat = trackFormat.buildUpon().setAverageBitrate(icyHeaders.bitrate).build();
}
}
trackArray[i] = new TrackGroup(trackFormat);
}
- isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
- dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
- preparedState =
- new PreparedState(seekMap, new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
+ trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
prepared = true;
- listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
Assertions.checkNotNull(callback).onPrepared(this);
}
- private PreparedState getPreparedState() {
- return Assertions.checkNotNull(preparedState);
- }
-
private void copyLengthFromLoader(ExtractingLoadable loadable) {
if (length == C.LENGTH_UNSET) {
length = loadable.length;
@@ -753,9 +759,8 @@
private void startLoading() {
ExtractingLoadable loadable =
new ExtractingLoadable(
- uri, dataSource, extractorHolder, /* extractorOutput= */ this, loadCondition);
+ uri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition);
if (prepared) {
- SeekMap seekMap = getPreparedState().seekMap;
Assertions.checkState(isPendingReset());
if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {
loadingFinished = true;
@@ -763,7 +768,8 @@
return;
}
loadable.setLoadPosition(
- seekMap.getSeekPoints(pendingResetPositionUs).first.position, pendingResetPositionUs);
+ Assertions.checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
+ pendingResetPositionUs);
pendingResetPositionUs = C.TIME_UNSET;
}
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
@@ -870,6 +876,13 @@
return pendingResetPositionUs != C.TIME_UNSET;
}
+ @EnsuresNonNull({"trackState", "seekMap"})
+ private void assertPrepared() {
+ Assertions.checkState(prepared);
+ Assertions.checkNotNull(trackState);
+ Assertions.checkNotNull(seekMap);
+ }
+
private final class SampleStreamImpl implements SampleStream {
private final int track;
@@ -906,7 +919,7 @@
private final Uri uri;
private final StatsDataSource dataSource;
- private final ExtractorHolder extractorHolder;
+ private final ProgressiveMediaExtractor progressiveMediaExtractor;
private final ExtractorOutput extractorOutput;
private final ConditionVariable loadCondition;
private final PositionHolder positionHolder;
@@ -924,12 +937,12 @@
public ExtractingLoadable(
Uri uri,
DataSource dataSource,
- ExtractorHolder extractorHolder,
+ ProgressiveMediaExtractor progressiveMediaExtractor,
ExtractorOutput extractorOutput,
ConditionVariable loadCondition) {
this.uri = uri;
this.dataSource = new StatsDataSource(dataSource);
- this.extractorHolder = extractorHolder;
+ this.progressiveMediaExtractor = progressiveMediaExtractor;
this.extractorOutput = extractorOutput;
this.loadCondition = loadCondition;
this.positionHolder = new PositionHolder();
@@ -946,10 +959,9 @@
}
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
- ExtractorInput input = null;
try {
long position = positionHolder.position;
dataSpec = buildDataSpec(position);
@@ -957,7 +969,6 @@
if (length != C.LENGTH_UNSET) {
length += position;
}
- Uri uri = Assertions.checkNotNull(dataSource.getUri());
icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders());
DataSource extractorDataSource = dataSource;
if (icyHeaders != null && icyHeaders.metadataInterval != C.LENGTH_UNSET) {
@@ -965,23 +976,27 @@
icyTrackOutput = icyTrack();
icyTrackOutput.format(ICY_FORMAT);
}
- input = new DefaultExtractorInput(extractorDataSource, position, length);
- Extractor extractor = extractorHolder.selectExtractor(input, extractorOutput, uri);
+ progressiveMediaExtractor.init(
+ extractorDataSource, uri, position, length, extractorOutput);
- // MP3 live streams commonly have seekable metadata, despite being unseekable.
- if (icyHeaders != null && extractor instanceof Mp3Extractor) {
- ((Mp3Extractor) extractor).disableSeeking();
+ if (icyHeaders != null) {
+ progressiveMediaExtractor.disableSeekingOnMp3Streams();
}
if (pendingExtractorSeek) {
- extractor.seek(position, seekTimeUs);
+ progressiveMediaExtractor.seek(position, seekTimeUs);
pendingExtractorSeek = false;
}
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
- loadCondition.block();
- result = extractor.read(input, positionHolder);
- if (input.getPosition() > position + continueLoadingCheckIntervalBytes) {
- position = input.getPosition();
+ try {
+ loadCondition.block();
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException();
+ }
+ result = progressiveMediaExtractor.read(positionHolder);
+ long currentInputPosition = progressiveMediaExtractor.getCurrentInputPosition();
+ if (currentInputPosition > position + continueLoadingCheckIntervalBytes) {
+ position = currentInputPosition;
loadCondition.close();
handler.post(onContinueLoadingRequestedRunnable);
}
@@ -989,8 +1004,8 @@
} finally {
if (result == Extractor.RESULT_SEEK) {
result = Extractor.RESULT_CONTINUE;
- } else if (input != null) {
- positionHolder.position = input.getPosition();
+ } else if (progressiveMediaExtractor.getCurrentInputPosition() != C.POSITION_UNSET) {
+ positionHolder.position = progressiveMediaExtractor.getCurrentInputPosition();
}
Util.closeQuietly(dataSource);
}
@@ -1018,13 +1033,14 @@
private DataSpec buildDataSpec(long position) {
// Disable caching if the content length cannot be resolved, since this is indicative of a
// progressive live stream.
- return new DataSpec(
- uri,
- position,
- C.LENGTH_UNSET,
- customCacheKey,
- DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN | DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION,
- ICY_METADATA_HEADERS);
+ return new DataSpec.Builder()
+ .setUri(uri)
+ .setPosition(position)
+ .setKey(customCacheKey)
+ .setFlags(
+ DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN | DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
+ .setHttpRequestHeaders(ICY_METADATA_HEADERS)
+ .build();
}
private void setLoadPosition(long position, long timeUs) {
@@ -1035,87 +1051,15 @@
}
}
- /** Stores a list of extractors and a selected extractor when the format has been detected. */
- private static final class ExtractorHolder {
+ /** Stores track state. */
+ private static final class TrackState {
- private final Extractor[] extractors;
-
- @Nullable private Extractor extractor;
-
- /**
- * Creates a holder that will select an extractor and initialize it using the specified output.
- *
- * @param extractors One or more extractors to choose from.
- */
- public ExtractorHolder(Extractor[] extractors) {
- this.extractors = extractors;
- }
-
- /**
- * Returns an initialized extractor for reading {@code input}, and returns the same extractor on
- * later calls.
- *
- * @param input The {@link ExtractorInput} from which data should be read.
- * @param output The {@link ExtractorOutput} that will be used to initialize the selected
- * extractor.
- * @param uri The {@link Uri} of the data.
- * @return An initialized extractor for reading {@code input}.
- * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected.
- * @throws IOException Thrown if the input could not be read.
- * @throws InterruptedException Thrown if the thread was interrupted.
- */
- public Extractor selectExtractor(ExtractorInput input, ExtractorOutput output, Uri uri)
- throws IOException, InterruptedException {
- if (extractor != null) {
- return extractor;
- }
- if (extractors.length == 1) {
- this.extractor = extractors[0];
- } else {
- for (Extractor extractor : extractors) {
- try {
- if (extractor.sniff(input)) {
- this.extractor = extractor;
- break;
- }
- } catch (EOFException e) {
- // Do nothing.
- } finally {
- input.resetPeekPosition();
- }
- }
- if (extractor == null) {
- throw new UnrecognizedInputFormatException(
- "None of the available extractors ("
- + Util.getCommaDelimitedSimpleClassNames(extractors)
- + ") could read the stream.",
- uri);
- }
- }
- extractor.init(output);
- return extractor;
- }
-
- public void release() {
- if (extractor != null) {
- extractor.release();
- extractor = null;
- }
- }
- }
-
- /** Stores state that is initialized when preparation completes. */
- private static final class PreparedState {
-
- public final SeekMap seekMap;
public final TrackGroupArray tracks;
public final boolean[] trackIsAudioVideoFlags;
public final boolean[] trackEnabledStates;
public final boolean[] trackNotifiedDownstreamFormats;
- public PreparedState(
- SeekMap seekMap, TrackGroupArray tracks, boolean[] trackIsAudioVideoFlags) {
- this.seekMap = seekMap;
+ public TrackState(TrackGroupArray tracks, boolean[] trackIsAudioVideoFlags) {
this.tracks = tracks;
this.trackIsAudioVideoFlags = trackIsAudioVideoFlags;
this.trackEnabledStates = new boolean[tracks.length];
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
index 3c1193c..b2404db 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
@@ -18,6 +18,8 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
@@ -28,7 +30,7 @@
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
-import java.io.IOException;
+import com.google.android.exoplayer2.util.Assertions;
/**
* Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}.
@@ -50,7 +52,7 @@
private final DataSource.Factory dataSourceFactory;
private ExtractorsFactory extractorsFactory;
- private DrmSessionManager<?> drmSessionManager;
+ private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private int continueLoadingCheckIntervalBytes;
@Nullable private String customCacheKey;
@@ -106,13 +108,10 @@
}
/**
- * Sets a tag for the media source which will be published in the {@link
- * com.google.android.exoplayer2.Timeline} of the source as {@link
- * com.google.android.exoplayer2.Timeline.Window#tag}.
- *
- * @param tag A tag for the media source.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
@@ -157,7 +156,7 @@
* @return This factory, for convenience.
*/
@Override
- public Factory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager) {
+ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager =
drmSessionManager != null
? drmSessionManager
@@ -165,23 +164,33 @@
return this;
}
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public ProgressiveMediaSource createMediaSource(Uri uri) {
+ return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
+ }
+
/**
* Returns a new {@link ProgressiveMediaSource} using the current parameters.
*
- * @param uri The {@link Uri}.
+ * @param mediaItem The {@link MediaItem}.
* @return The new {@link ProgressiveMediaSource}.
+ * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
- public ProgressiveMediaSource createMediaSource(Uri uri) {
+ public ProgressiveMediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
return new ProgressiveMediaSource(
- uri,
+ mediaItem.playbackProperties.sourceUri,
dataSourceFactory,
extractorsFactory,
drmSessionManager,
loadErrorHandlingPolicy,
customCacheKey,
continueLoadingCheckIntervalBytes,
- tag);
+ mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
@Override
@@ -199,12 +208,13 @@
private final Uri uri;
private final DataSource.Factory dataSourceFactory;
private final ExtractorsFactory extractorsFactory;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy;
@Nullable private final String customCacheKey;
private final int continueLoadingCheckIntervalBytes;
@Nullable private final Object tag;
+ private boolean timelineIsPlaceholder;
private long timelineDurationUs;
private boolean timelineIsSeekable;
private boolean timelineIsLive;
@@ -215,7 +225,7 @@
Uri uri,
DataSource.Factory dataSourceFactory,
ExtractorsFactory extractorsFactory,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy,
@Nullable String customCacheKey,
int continueLoadingCheckIntervalBytes,
@@ -227,6 +237,7 @@
this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy;
this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
+ this.timelineIsPlaceholder = true;
this.timelineDurationUs = C.TIME_UNSET;
this.tag = tag;
}
@@ -241,11 +252,11 @@
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener;
drmSessionManager.prepare();
- notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive);
+ notifySourceInfoRefreshed();
}
@Override
- public void maybeThrowSourceInfoRefreshError() throws IOException {
+ public void maybeThrowSourceInfoRefreshError() {
// Do nothing.
}
@@ -284,30 +295,47 @@
public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
// If we already have the duration from a previous source info refresh, use it.
durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
- if (timelineDurationUs == durationUs
+ if (!timelineIsPlaceholder
+ && timelineDurationUs == durationUs
&& timelineIsSeekable == isSeekable
&& timelineIsLive == isLive) {
// Suppress no-op source info changes.
return;
}
- notifySourceInfoRefreshed(durationUs, isSeekable, isLive);
+ timelineDurationUs = durationUs;
+ timelineIsSeekable = isSeekable;
+ timelineIsLive = isLive;
+ timelineIsPlaceholder = false;
+ notifySourceInfoRefreshed();
}
// Internal methods.
- private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
- timelineDurationUs = durationUs;
- timelineIsSeekable = isSeekable;
- timelineIsLive = isLive;
+ private void notifySourceInfoRefreshed() {
// TODO: Split up isDynamic into multiple fields to indicate which values may change. Then
// indicate that the duration may change until it's known. See [internal: b/69703223].
- refreshSourceInfo(
+ Timeline timeline =
new SinglePeriodTimeline(
timelineDurationUs,
timelineIsSeekable,
/* isDynamic= */ false,
/* isLive= */ timelineIsLive,
/* manifest= */ null,
- tag));
+ tag);
+ if (timelineIsPlaceholder) {
+ // TODO: Actually prepare the extractors during prepatation so that we don't need a
+ // placeholder. See https://github.com/google/ExoPlayer/issues/4727.
+ timeline =
+ new ForwardingTimeline(timeline) {
+ @Override
+ public Window getWindow(
+ int windowIndex, Window window, long defaultPositionProjectionUs) {
+ super.getWindow(windowIndex, window, defaultPositionProjectionUs);
+ window.isPlaceholder = true;
+ return window;
+ }
+ };
+ }
+ refreshSourceInfo(timeline);
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java
index 68761ce..7fd95df 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java
@@ -17,16 +17,19 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
import com.google.android.exoplayer2.source.SampleQueue.SampleExtrasHolder;
import com.google.android.exoplayer2.upstream.Allocation;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Arrays;
/** A queue of media sample data. */
/* package */ class SampleDataQueue {
@@ -174,8 +177,7 @@
return totalBytesWritten;
}
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException {
length = preAppend(length);
int bytesAppended =
input.read(
@@ -228,10 +230,14 @@
int ivSize = signalByte & 0x7F;
// Read the initialization vector.
- if (buffer.cryptoInfo.iv == null) {
- buffer.cryptoInfo.iv = new byte[16];
+ CryptoInfo cryptoInfo = buffer.cryptoInfo;
+ if (cryptoInfo.iv == null) {
+ cryptoInfo.iv = new byte[16];
+ } else {
+ // Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
+ Arrays.fill(cryptoInfo.iv, (byte) 0);
}
- readData(offset, buffer.cryptoInfo.iv, ivSize);
+ readData(offset, cryptoInfo.iv, ivSize);
offset += ivSize;
// Read the subsample count, if present.
@@ -246,11 +252,11 @@
}
// Write the clear and encrypted subsample sizes.
- int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData;
+ @Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
clearDataSizes = new int[subsampleCount];
}
- int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData;
+ @Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
encryptedDataSizes = new int[subsampleCount];
}
@@ -270,13 +276,13 @@
}
// Populate the cryptoInfo.
- CryptoData cryptoData = extrasHolder.cryptoData;
- buffer.cryptoInfo.set(
+ CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
+ cryptoInfo.set(
subsampleCount,
clearDataSizes,
encryptedDataSizes,
cryptoData.encryptionKey,
- buffer.cryptoInfo.iv,
+ cryptoInfo.iv,
cryptoData.cryptoMode,
cryptoData.encryptedBlocks,
cryptoData.clearBlocks);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
index 28f5fb0..484aca5 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source;
import android.os.Looper;
+import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
@@ -25,14 +26,16 @@
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
/** A queue of media samples. */
public class SampleQueue implements TrackOutput {
@@ -52,11 +55,12 @@
private final SampleDataQueue sampleDataQueue;
private final SampleExtrasHolder extrasHolder;
- private final DrmSessionManager<?> drmSessionManager;
- private UpstreamFormatChangedListener upstreamFormatChangeListener;
+ private final DrmSessionManager drmSessionManager;
+ private final MediaSourceEventDispatcher eventDispatcher;
+ @Nullable private UpstreamFormatChangedListener upstreamFormatChangeListener;
@Nullable private Format downstreamFormat;
- @Nullable private DrmSession<?> currentDrmSession;
+ @Nullable private DrmSession currentDrmSession;
private int capacity;
private int[] sourceIds;
@@ -64,7 +68,7 @@
private int[] sizes;
private int[] flags;
private long[] timesUs;
- private CryptoData[] cryptoDatas;
+ private @NullableType CryptoData[] cryptoDatas;
private Format[] formats;
private int length;
@@ -77,12 +81,12 @@
private boolean isLastSampleQueued;
private boolean upstreamKeyframeRequired;
private boolean upstreamFormatRequired;
- private Format upstreamFormat;
- private Format upstreamCommittedFormat;
+ private boolean upstreamFormatAdjustmentRequired;
+ @Nullable private Format unadjustedUpstreamFormat;
+ @Nullable private Format upstreamFormat;
+ @Nullable private Format upstreamCommittedFormat;
private int upstreamSourceId;
- private boolean pendingFormatAdjustment;
- private Format lastUnadjustedFormat;
private long sampleOffsetUs;
private boolean pendingSplice;
@@ -92,10 +96,16 @@
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
* @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions}
* from. The created instance does not take ownership of this {@link DrmSessionManager}.
+ * @param eventDispatcher A {@link MediaSourceEventDispatcher} to notify of events related to this
+ * SampleQueue.
*/
- public SampleQueue(Allocator allocator, DrmSessionManager<?> drmSessionManager) {
+ public SampleQueue(
+ Allocator allocator,
+ DrmSessionManager drmSessionManager,
+ MediaSourceEventDispatcher eventDispatcher) {
sampleDataQueue = new SampleDataQueue(allocator);
this.drmSessionManager = drmSessionManager;
+ this.eventDispatcher = eventDispatcher;
extrasHolder = new SampleExtrasHolder();
capacity = SAMPLE_CAPACITY_INCREMENT;
sourceIds = new int[capacity];
@@ -113,16 +123,15 @@
// Called by the consuming thread when there is no loading thread.
- /**
- * Calls {@link #reset(boolean) reset(true)} and releases any owned {@link DrmSession} references.
- */
+ /** Calls {@link #reset(boolean) reset(true)} and releases any resources owned by the queue. */
+ @CallSuper
public void release() {
reset(/* resetUpstreamFormat= */ true);
releaseDrmSessionReferences();
}
- /** Resets the output without clearing the upstream format. Equivalent to {@code reset(false)}. */
- public void reset() {
+ /** Convenience method for {@code reset(false)}. */
+ public final void reset() {
reset(/* resetUpstreamFormat= */ false);
}
@@ -134,6 +143,7 @@
* are assumed to have the current upstream format. If set to true, {@link #format(Format)}
* must be called after the reset before any more samples can be queued.
*/
+ @CallSuper
public void reset(boolean resetUpstreamFormat) {
sampleDataQueue.reset();
length = 0;
@@ -146,6 +156,7 @@
isLastSampleQueued = false;
upstreamCommittedFormat = null;
if (resetUpstreamFormat) {
+ unadjustedUpstreamFormat = null;
upstreamFormat = null;
upstreamFormatRequired = true;
}
@@ -156,17 +167,17 @@
*
* @param sourceId The source identifier.
*/
- public void sourceId(int sourceId) {
+ public final void sourceId(int sourceId) {
upstreamSourceId = sourceId;
}
/** Indicates samples that are subsequently queued should be spliced into those already queued. */
- public void splice() {
+ public final void splice() {
pendingSplice = true;
}
/** Returns the current absolute write index. */
- public int getWriteIndex() {
+ public final int getWriteIndex() {
return absoluteFirstIndex + length;
}
@@ -176,13 +187,14 @@
* @param discardFromIndex The absolute index of the first sample to be discarded. Must be in the
* range [{@link #getReadIndex()}, {@link #getWriteIndex()}].
*/
- public void discardUpstreamSamples(int discardFromIndex) {
+ public final void discardUpstreamSamples(int discardFromIndex) {
sampleDataQueue.discardUpstreamSampleBytes(discardUpstreamSampleMetadata(discardFromIndex));
}
// Called by the consuming thread.
- /** Calls {@link #discardToEnd()} and releases any owned {@link DrmSession} references. */
+ /** Calls {@link #discardToEnd()} and releases any resources owned by the queue. */
+ @CallSuper
public void preRelease() {
discardToEnd();
releaseDrmSessionReferences();
@@ -193,6 +205,7 @@
*
* @throws IOException The underlying error.
*/
+ @CallSuper
public void maybeThrowError() throws IOException {
// TODO: Avoid throwing if the DRM error is not preventing a read operation.
if (currentDrmSession != null && currentDrmSession.getState() == DrmSession.STATE_ERROR) {
@@ -201,12 +214,12 @@
}
/** Returns the current absolute start index. */
- public int getFirstIndex() {
+ public final int getFirstIndex() {
return absoluteFirstIndex;
}
/** Returns the current absolute read index. */
- public int getReadIndex() {
+ public final int getReadIndex() {
return absoluteFirstIndex + readPosition;
}
@@ -216,13 +229,14 @@
*
* @return The source id.
*/
- public synchronized int peekSourceId() {
+ public final synchronized int peekSourceId() {
int relativeReadIndex = getRelativeIndex(readPosition);
return hasNextSample() ? sourceIds[relativeReadIndex] : upstreamSourceId;
}
/** Returns the upstream {@link Format} in which samples are being queued. */
- public synchronized Format getUpstreamFormat() {
+ @Nullable
+ public final synchronized Format getUpstreamFormat() {
return upstreamFormatRequired ? null : upstreamFormat;
}
@@ -236,7 +250,7 @@
* @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no
* samples have been queued.
*/
- public synchronized long getLargestQueuedTimestampUs() {
+ public final synchronized long getLargestQueuedTimestampUs() {
return largestQueuedTimestampUs;
}
@@ -249,12 +263,12 @@
* considered as having been queued. Samples that were dequeued from the front of the queue are
* considered as having been queued.
*/
- public synchronized boolean isLastSampleQueued() {
+ public final synchronized boolean isLastSampleQueued() {
return isLastSampleQueued;
}
/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
- public synchronized long getFirstTimestampUs() {
+ public final synchronized long getFirstTimestampUs() {
return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex];
}
@@ -270,6 +284,7 @@
* queue is empty.
*/
@SuppressWarnings("ReferenceEquality") // See comments in setUpstreamFormat
+ @CallSuper
public synchronized boolean isReady(boolean loadingFinished) {
if (!hasNextSample()) {
return loadingFinished
@@ -311,6 +326,7 @@
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
+ @CallSuper
public int read(
FormatHolder formatHolder,
DecoderInputBuffer buffer,
@@ -332,7 +348,7 @@
* @param sampleIndex The sample index.
* @return Whether the seek was successful.
*/
- public synchronized boolean seekTo(int sampleIndex) {
+ public final synchronized boolean seekTo(int sampleIndex) {
rewind();
if (sampleIndex < absoluteFirstIndex || sampleIndex > absoluteFirstIndex + length) {
return false;
@@ -349,7 +365,7 @@
* end of the queue, by seeking to the last sample (or keyframe).
* @return Whether the seek was successful.
*/
- public synchronized boolean seekTo(long timeUs, boolean allowTimeBeyondBuffer) {
+ public final synchronized boolean seekTo(long timeUs, boolean allowTimeBeyondBuffer) {
rewind();
int relativeReadIndex = getRelativeIndex(readPosition);
if (!hasNextSample()
@@ -372,7 +388,7 @@
* @param timeUs The time to advance to.
* @return The number of samples that were skipped, which may be equal to 0.
*/
- public synchronized int advanceTo(long timeUs) {
+ public final synchronized int advanceTo(long timeUs) {
int relativeReadIndex = getRelativeIndex(readPosition);
if (!hasNextSample() || timeUs < timesUs[relativeReadIndex]) {
return 0;
@@ -391,7 +407,7 @@
*
* @return The number of samples that were skipped.
*/
- public synchronized int advanceToEnd() {
+ public final synchronized int advanceToEnd() {
int skipCount = length - readPosition;
readPosition = length;
return skipCount;
@@ -407,18 +423,18 @@
* position. If false then samples at and beyond the read position may be discarded, in which
* case the read position is advanced to the first remaining sample.
*/
- public void discardTo(long timeUs, boolean toKeyframe, boolean stopAtReadPosition) {
+ public final void discardTo(long timeUs, boolean toKeyframe, boolean stopAtReadPosition) {
sampleDataQueue.discardDownstreamTo(
discardSampleMetadataTo(timeUs, toKeyframe, stopAtReadPosition));
}
/** Discards up to but not including the read position. */
- public void discardToRead() {
+ public final void discardToRead() {
sampleDataQueue.discardDownstreamTo(discardSampleMetadataToRead());
}
/** Discards all samples in the queue and advances the read position. */
- public void discardToEnd() {
+ public final void discardToEnd() {
sampleDataQueue.discardDownstreamTo(discardSampleMetadataToEnd());
}
@@ -430,10 +446,10 @@
*
* @param sampleOffsetUs The timestamp offset in microseconds.
*/
- public void setSampleOffsetUs(long sampleOffsetUs) {
+ public final void setSampleOffsetUs(long sampleOffsetUs) {
if (this.sampleOffsetUs != sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
- pendingFormatAdjustment = true;
+ invalidateUpstreamFormatAdjustment();
}
}
@@ -442,31 +458,32 @@
*
* @param listener The listener.
*/
- public void setUpstreamFormatChangeListener(UpstreamFormatChangedListener listener) {
+ public final void setUpstreamFormatChangeListener(
+ @Nullable UpstreamFormatChangedListener listener) {
upstreamFormatChangeListener = listener;
}
// TrackOutput implementation. Called by the loading thread.
@Override
- public void format(Format unadjustedFormat) {
- Format adjustedFormat = getAdjustedSampleFormat(unadjustedFormat, sampleOffsetUs);
- boolean formatChanged = setUpstreamFormat(adjustedFormat);
- lastUnadjustedFormat = unadjustedFormat;
- pendingFormatAdjustment = false;
- if (upstreamFormatChangeListener != null && formatChanged) {
- upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat);
+ public final void format(Format unadjustedUpstreamFormat) {
+ Format adjustedUpstreamFormat = getAdjustedUpstreamFormat(unadjustedUpstreamFormat);
+ upstreamFormatAdjustmentRequired = false;
+ this.unadjustedUpstreamFormat = unadjustedUpstreamFormat;
+ boolean upstreamFormatChanged = setUpstreamFormat(adjustedUpstreamFormat);
+ if (upstreamFormatChangeListener != null && upstreamFormatChanged) {
+ upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedUpstreamFormat);
}
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public final int sampleData(DataReader input, int length, boolean allowEndOfInput)
+ throws IOException {
return sampleDataQueue.sampleData(input, length, allowEndOfInput);
}
@Override
- public void sampleData(ParsableByteArray buffer, int length) {
+ public final void sampleData(ParsableByteArray buffer, int length) {
sampleDataQueue.sampleData(buffer, length);
}
@@ -477,8 +494,8 @@
int size,
int offset,
@Nullable CryptoData cryptoData) {
- if (pendingFormatAdjustment) {
- format(lastUnadjustedFormat);
+ if (upstreamFormatAdjustmentRequired) {
+ format(Assertions.checkStateNotNull(unadjustedUpstreamFormat));
}
timeUs += sampleOffsetUs;
if (pendingSplice) {
@@ -491,6 +508,36 @@
commitSample(timeUs, flags, absoluteOffset, size, cryptoData);
}
+ /**
+ * Invalidates the last upstream format adjustment. {@link #getAdjustedUpstreamFormat(Format)}
+ * will be called to adjust the upstream {@link Format} again before the next sample is queued.
+ */
+ protected final void invalidateUpstreamFormatAdjustment() {
+ upstreamFormatAdjustmentRequired = true;
+ }
+
+ /**
+ * Adjusts the upstream {@link Format} (i.e., the {@link Format} that was most recently passed to
+ * {@link #format(Format)}).
+ *
+ * <p>The default implementation incorporates the sample offset passed to {@link
+ * #setSampleOffsetUs(long)} into {@link Format#subsampleOffsetUs}.
+ *
+ * @param format The {@link Format} to adjust.
+ * @return The adjusted {@link Format}.
+ */
+ @CallSuper
+ protected Format getAdjustedUpstreamFormat(Format format) {
+ if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
+ format =
+ format
+ .buildUpon()
+ .setSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs)
+ .build();
+ }
+ return format;
+ }
+
// Internal methods.
/** Rewinds the read position to the first sample in the queue. */
@@ -507,7 +554,7 @@
boolean loadingFinished,
long decodeOnlyUntilUs,
SampleExtrasHolder extrasHolder) {
-
+ buffer.waitingForKeys = false;
// This is a temporary fix for https://github.com/google/ExoPlayer/issues/6155.
// TODO: Remove it and replace it with a fix that discards samples when writing to the queue.
boolean hasNextSample;
@@ -541,6 +588,7 @@
}
if (!mayReadSample(relativeReadIndex)) {
+ buffer.waitingForKeys = true;
return C.RESULT_NOTHING_READ;
}
@@ -561,10 +609,6 @@
}
private synchronized boolean setUpstreamFormat(Format format) {
- if (format == null) {
- upstreamFormatRequired = true;
- return false;
- }
upstreamFormatRequired = false;
if (Util.areEqual(format, upstreamFormat)) {
// The format is unchanged. If format and upstreamFormat are different objects, we keep the
@@ -612,7 +656,7 @@
private void releaseDrmSessionReferences() {
if (currentDrmSession != null) {
- currentDrmSession.release();
+ currentDrmSession.release(eventDispatcher);
currentDrmSession = null;
// Clear downstream format to avoid violating the assumption that downstreamFormat.drmInitData
// != null implies currentSession != null
@@ -621,7 +665,11 @@
}
private synchronized void commitSample(
- long timeUs, @C.BufferFlags int sampleFlags, long offset, int size, CryptoData cryptoData) {
+ long timeUs,
+ @C.BufferFlags int sampleFlags,
+ long offset,
+ int size,
+ @Nullable CryptoData cryptoData) {
if (upstreamKeyframeRequired) {
if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
return;
@@ -738,17 +786,9 @@
private void onFormatResult(Format newFormat, FormatHolder outputFormatHolder) {
outputFormatHolder.format = newFormat;
boolean isFirstFormat = downstreamFormat == null;
- DrmInitData oldDrmInitData = isFirstFormat ? null : downstreamFormat.drmInitData;
+ @Nullable DrmInitData oldDrmInitData = isFirstFormat ? null : downstreamFormat.drmInitData;
downstreamFormat = newFormat;
- if (drmSessionManager == DrmSessionManager.DUMMY) {
- // Avoid attempting to acquire a session using the dummy DRM session manager. It's likely that
- // the media source creation has not yet been migrated and the renderer can acquire the
- // session for the read DRM init data.
- // TODO: Remove once renderers are migrated [Internal ref: b/122519809].
- return;
- }
- DrmInitData newDrmInitData = newFormat.drmInitData;
- outputFormatHolder.includesDrmSession = true;
+ @Nullable DrmInitData newDrmInitData = newFormat.drmInitData;
outputFormatHolder.drmSession = currentDrmSession;
if (!isFirstFormat && Util.areEqual(oldDrmInitData, newDrmInitData)) {
// Nothing to do.
@@ -756,17 +796,17 @@
}
// Ensure we acquire the new session before releasing the previous one in case the same session
// is being used for both DrmInitData.
- DrmSession<?> previousSession = currentDrmSession;
+ @Nullable DrmSession previousSession = currentDrmSession;
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
currentDrmSession =
newDrmInitData != null
- ? drmSessionManager.acquireSession(playbackLooper, newDrmInitData)
+ ? drmSessionManager.acquireSession(playbackLooper, eventDispatcher, newDrmInitData)
: drmSessionManager.acquirePlaceholderSession(
playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
outputFormatHolder.drmSession = currentDrmSession;
if (previousSession != null) {
- previousSession.release();
+ previousSession.release(eventDispatcher);
}
}
@@ -777,12 +817,6 @@
* @return Whether it's possible to read the next sample.
*/
private boolean mayReadSample(int relativeReadIndex) {
- if (drmSessionManager == DrmSessionManager.DUMMY) {
- // TODO: Remove once renderers are migrated [Internal ref: b/122519809].
- // For protected content it's likely that the DrmSessionManager is still being injected into
- // the renderers. We assume that the renderers will be able to acquire a DrmSession if needed.
- return true;
- }
return currentDrmSession == null
|| currentDrmSession.getState() == DrmSession.STATE_OPENED_WITH_KEYS
|| ((flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) == 0
@@ -883,28 +917,11 @@
return relativeIndex < capacity ? relativeIndex : relativeIndex - capacity;
}
- /**
- * Adjusts a {@link Format} to incorporate a sample offset into {@link Format#subsampleOffsetUs}.
- *
- * @param format The {@link Format} to adjust.
- * @param sampleOffsetUs The offset to apply.
- * @return The adjusted {@link Format}.
- */
- private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) {
- if (format == null) {
- return null;
- }
- if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
- format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
- }
- return format;
- }
-
/** A holder for sample metadata not held by {@link DecoderInputBuffer}. */
/* package */ static final class SampleExtrasHolder {
public int size;
public long offset;
- public CryptoData cryptoData;
+ @Nullable public CryptoData cryptoData;
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java
index 54293aa..0c5e504 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java
@@ -15,16 +15,26 @@
*/
package com.google.android.exoplayer2.source;
+import androidx.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import java.io.IOException;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* A stream of media samples (and associated format information).
*/
public interface SampleStream {
+ /** Return values of {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({C.RESULT_NOTHING_READ, C.RESULT_FORMAT_READ, C.RESULT_BUFFER_READ})
+ @interface ReadDataResult {}
+
/**
* Returns whether data is available to be read.
* <p>
@@ -62,9 +72,9 @@
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
- * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
- * {@link C#RESULT_BUFFER_READ}.
+ * @return The status of read, one of {@link ReadDataResult}.
*/
+ @ReadDataResult
int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired);
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
index abaf336..839f683 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
@@ -34,24 +34,17 @@
public final class SilenceMediaSource extends BaseMediaSource {
private static final int SAMPLE_RATE_HZ = 44100;
- @C.PcmEncoding private static final int ENCODING = C.ENCODING_PCM_16BIT;
+ @C.PcmEncoding private static final int PCM_ENCODING = C.ENCODING_PCM_16BIT;
private static final int CHANNEL_COUNT = 2;
private static final Format FORMAT =
- Format.createAudioSampleFormat(
- /* id=*/ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- CHANNEL_COUNT,
- SAMPLE_RATE_HZ,
- ENCODING,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setChannelCount(CHANNEL_COUNT)
+ .setSampleRate(SAMPLE_RATE_HZ)
+ .setPcmEncoding(PCM_ENCODING)
+ .build();
private static final byte[] SILENCE_SAMPLE =
- new byte[Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT) * 1024];
+ new byte[Util.getPcmFrameSize(PCM_ENCODING, CHANNEL_COUNT) * 1024];
private final long durationUs;
@@ -243,11 +236,11 @@
private static long getAudioByteCount(long durationUs) {
long audioSampleCount = durationUs * SAMPLE_RATE_HZ / C.MICROS_PER_SECOND;
- return Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT) * audioSampleCount;
+ return Util.getPcmFrameSize(PCM_ENCODING, CHANNEL_COUNT) * audioSampleCount;
}
private static long getAudioPositionUs(long bytes) {
- long audioSampleCount = bytes / Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT);
+ long audioSampleCount = bytes / Util.getPcmFrameSize(PCM_ENCODING, CHANNEL_COUNT);
return audioSampleCount * C.MICROS_PER_SECOND / SAMPLE_RATE_HZ;
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java
index a5d8266..8fb5d38 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java
@@ -104,7 +104,7 @@
}
@Override
- public void maybeThrowPrepareError() throws IOException {
+ public void maybeThrowPrepareError() {
// Do nothing.
}
@@ -212,8 +212,8 @@
// Loader.Callback implementation.
@Override
- public void onLoadCompleted(SourceLoadable loadable, long elapsedRealtimeMs,
- long loadDurationMs) {
+ public void onLoadCompleted(
+ SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
sampleSize = (int) loadable.dataSource.getBytesRead();
sampleData = Assertions.checkNotNull(loadable.sampleData);
loadingFinished = true;
@@ -234,8 +234,8 @@
}
@Override
- public void onLoadCanceled(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs,
- boolean released) {
+ public void onLoadCanceled(
+ SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
eventDispatcher.loadCanceled(
loadable.dataSpec,
loadable.dataSource.getLastOpenedUri(),
@@ -394,7 +394,7 @@
}
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
// We always load from the beginning, so reset bytesRead to 0.
dataSource.resetBytesRead();
try {
@@ -415,7 +415,5 @@
Util.closeQuietly(dataSource);
}
}
-
}
-
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
index db14149..4365c8f 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
@@ -59,7 +59,6 @@
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private boolean treatLoadErrorsAsEndOfStream;
- private boolean isCreateCalled;
@Nullable private Object tag;
/**
@@ -81,8 +80,7 @@
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
- public Factory setTag(Object tag) {
- Assertions.checkState(!isCreateCalled);
+ public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
}
@@ -115,9 +113,12 @@
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
- public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
- Assertions.checkState(!isCreateCalled);
- this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
+ public Factory setLoadErrorHandlingPolicy(
+ @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
+ this.loadErrorHandlingPolicy =
+ loadErrorHandlingPolicy != null
+ ? loadErrorHandlingPolicy
+ : new DefaultLoadErrorHandlingPolicy();
return this;
}
@@ -132,7 +133,6 @@
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTreatLoadErrorsAsEndOfStream(boolean treatLoadErrorsAsEndOfStream) {
- Assertions.checkState(!isCreateCalled);
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
return this;
}
@@ -146,7 +146,6 @@
* @return The new {@link SingleSampleMediaSource}.
*/
public SingleSampleMediaSource createMediaSource(Uri uri, Format format, long durationUs) {
- isCreateCalled = true;
return new SingleSampleMediaSource(
uri,
dataSourceFactory,
@@ -257,8 +256,8 @@
Format format,
long durationUs,
int minLoadableRetryCount,
- Handler eventHandler,
- EventListener eventListener,
+ @Nullable Handler eventHandler,
+ @Nullable EventListener eventListener,
int eventSourceId,
boolean treatLoadErrorsAsEndOfStream) {
this(
@@ -288,7 +287,7 @@
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
this.tag = tag;
- dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
+ dataSpec = new DataSpec.Builder().setUri(uri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build();
timeline =
new SinglePeriodTimeline(
durationUs,
@@ -314,7 +313,7 @@
}
@Override
- public void maybeThrowSourceInfoRefreshError() throws IOException {
+ public void maybeThrowSourceInfoRefreshError() {
// Do nothing.
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
index 0a1628b..dee63d8 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
@@ -29,8 +29,7 @@
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
- * Represents ad group times relative to the start of the media and information on the state and
- * URIs of ads within each ad group.
+ * Represents ad group times and information on the state and URIs of ads within each ad group.
*
* <p>Instances are immutable. Call the {@code with*} methods to get new instances that have the
* required changes.
@@ -272,8 +271,9 @@
/** The number of ad groups. */
public final int adGroupCount;
/**
- * The times of ad groups, in microseconds. A final element with the value {@link
- * C#TIME_END_OF_SOURCE} indicates a postroll ad.
+ * The times of ad groups, in microseconds, relative to the start of the {@link
+ * com.google.android.exoplayer2.Timeline.Period} they belong to. A final element with the value
+ * {@link C#TIME_END_OF_SOURCE} indicates a postroll ad.
*/
public final long[] adGroupTimesUs;
/** The ad groups. */
@@ -286,8 +286,9 @@
/**
* Creates a new ad playback state with the specified ad group times.
*
- * @param adGroupTimesUs The times of ad groups in microseconds. A final element with the value
- * {@link C#TIME_END_OF_SOURCE} indicates that there is a postroll ad.
+ * @param adGroupTimesUs The times of ad groups in microseconds, relative to the start of the
+ * {@link com.google.android.exoplayer2.Timeline.Period} they belong to. A final element with
+ * the value {@link C#TIME_END_OF_SOURCE} indicates that there is a postroll ad.
*/
public AdPlaybackState(long... adGroupTimesUs) {
int count = adGroupTimesUs.length;
@@ -315,16 +316,18 @@
* unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has no
* ads remaining to be played, or if there is no such ad group.
*
- * @param positionUs The position at or before which to find an ad group, in microseconds, or
- * {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case the index of any
+ * @param positionUs The period position at or before which to find an ad group, in microseconds,
+ * or {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case the index of any
* unplayed postroll ad group will be returned).
+ * @param periodDurationUs The duration of the containing timeline period, in microseconds, or
+ * {@link C#TIME_UNSET} if not known.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
- public int getAdGroupIndexForPositionUs(long positionUs) {
+ public int getAdGroupIndexForPositionUs(long positionUs, long periodDurationUs) {
// Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE.
// In practice we expect there to be few ad groups so the search shouldn't be expensive.
int index = adGroupTimesUs.length - 1;
- while (index >= 0 && isPositionBeforeAdGroup(positionUs, index)) {
+ while (index >= 0 && isPositionBeforeAdGroup(positionUs, periodDurationUs, index)) {
index--;
}
return index >= 0 && adGroups[index].hasUnplayedAds() ? index : C.INDEX_UNSET;
@@ -334,11 +337,11 @@
* Returns the index of the next ad group after {@code positionUs} that has ads remaining to be
* played. Returns {@link C#INDEX_UNSET} if there is no such ad group.
*
- * @param positionUs The position after which to find an ad group, in microseconds, or {@link
- * C#TIME_END_OF_SOURCE} for the end of the stream (in which case there can be no ad group
- * after the position).
- * @param periodDurationUs The duration of the containing period in microseconds, or {@link
- * C#TIME_UNSET} if not known.
+ * @param positionUs The period position after which to find an ad group, in microseconds, or
+ * {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case there can be no ad
+ * group after the position).
+ * @param periodDurationUs The duration of the containing timeline period, in microseconds, or
+ * {@link C#TIME_UNSET} if not known.
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexAfterPositionUs(long positionUs, long periodDurationUs) {
@@ -425,7 +428,10 @@
return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
- /** Returns an instance with the specified ad resume position, in microseconds. */
+ /**
+ * Returns an instance with the specified ad resume position, in microseconds, relative to the
+ * start of the current ad.
+ */
@CheckResult
public AdPlaybackState withAdResumePositionUs(long adResumePositionUs) {
if (this.adResumePositionUs == adResumePositionUs) {
@@ -471,14 +477,15 @@
return result;
}
- private boolean isPositionBeforeAdGroup(long positionUs, int adGroupIndex) {
+ private boolean isPositionBeforeAdGroup(
+ long positionUs, long periodDurationUs, int adGroupIndex) {
if (positionUs == C.TIME_END_OF_SOURCE) {
// The end of the content is at (but not before) any postroll ad, and after any other ads.
return false;
}
long adGroupPositionUs = adGroupTimesUs[adGroupIndex];
if (adGroupPositionUs == C.TIME_END_OF_SOURCE) {
- return contentDurationUs == C.TIME_UNSET || positionUs < contentDurationUs;
+ return periodDurationUs == C.TIME_UNSET || positionUs < periodDurationUs;
} else {
return positionUs < adGroupPositionUs;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java
index f9e58e2..25877d1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java
@@ -46,12 +46,13 @@
public final Format trackFormat;
/**
* One of the {@link C} {@code SELECTION_REASON_*} constants if the chunk belongs to a track.
- * {@link C#SELECTION_REASON_UNKNOWN} if the chunk does not belong to a track.
+ * {@link C#SELECTION_REASON_UNKNOWN} if the chunk does not belong to a track, or if the selection
+ * reason is unknown.
*/
public final int trackSelectionReason;
/**
* Optional data associated with the selection of the track to which this chunk belongs. Null if
- * the chunk does not belong to a track.
+ * the chunk does not belong to a track, or if there is no associated track selection data.
*/
@Nullable public final Object trackSelectionData;
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
index 7fdc5d3..76a4665 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java
@@ -23,10 +23,10 @@
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
@@ -197,14 +197,14 @@
@Override
public void format(Format format) {
- sampleFormat = manifestFormat != null ? format.copyWithManifestFormatInfo(manifestFormat)
- : format;
+ sampleFormat =
+ manifestFormat != null ? format.withManifestFormatInfo(manifestFormat) : format;
castNonNull(trackOutput).format(sampleFormat);
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput)
+ throws IOException {
return castNonNull(trackOutput).sampleData(input, length, allowEndOfInput);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
index 577d987..fe7c583 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
@@ -110,7 +110,7 @@
Callback<ChunkSampleStream<T>> callback,
Allocator allocator,
long positionUs,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher) {
this.primaryTrackType = primaryTrackType;
@@ -131,13 +131,14 @@
int[] trackTypes = new int[1 + embeddedTrackCount];
SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount];
- primarySampleQueue = new SampleQueue(allocator, drmSessionManager);
+ primarySampleQueue = new SampleQueue(allocator, drmSessionManager, eventDispatcher);
trackTypes[0] = primaryTrackType;
sampleQueues[0] = primarySampleQueue;
for (int i = 0; i < embeddedTrackCount; i++) {
SampleQueue sampleQueue =
- new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager());
+ new SampleQueue(
+ allocator, DrmSessionManager.getDummyDrmSessionManager(), eventDispatcher);
embeddedSampleQueues[i] = sampleQueue;
sampleQueues[i + 1] = sampleQueue;
trackTypes[i + 1] = this.embeddedTrackTypes[i];
@@ -412,8 +413,8 @@
}
@Override
- public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
- boolean released) {
+ public void onLoadCanceled(
+ Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
eventDispatcher.loadCanceled(
loadable.dataSpec,
loadable.getUri(),
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
index 9dffe09..1b43af2 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
@@ -67,7 +68,7 @@
DataSpec dataSpec,
Format trackFormat,
int trackSelectionReason,
- Object trackSelectionData,
+ @Nullable Object trackSelectionData,
long startTimeUs,
long endTimeUs,
long clippedStartTimeUs,
@@ -111,7 +112,7 @@
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
- public final void load() throws IOException, InterruptedException {
+ public final void load() throws IOException {
if (nextLoadPosition == 0) {
// Configure the output and set it as the target for the extractor wrapper.
BaseMediaChunkOutput output = getOutput();
@@ -126,7 +127,7 @@
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
ExtractorInput input =
new DefaultExtractorInput(
- dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ dataSource, loadDataSpec.position, dataSource.open(loadDataSpec));
// Load and decode the sample data.
try {
Extractor extractor = extractorWrapper.extractor;
@@ -136,7 +137,7 @@
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
- nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ nextLoadPosition = input.getPosition() - dataSpec.position;
}
} finally {
Util.closeQuietly(dataSource);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java
index 4c22532..6d97c1d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java
@@ -77,7 +77,7 @@
}
@Override
- public final void load() throws IOException, InterruptedException {
+ public final void load() throws IOException {
try {
dataSource.open(dataSpec);
int limit = 0;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
index 178fb94..eedcad3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java
@@ -83,7 +83,7 @@
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
if (nextLoadPosition == 0) {
extractorWrapper.init(
trackOutputProvider, /* startTimeUs= */ C.TIME_UNSET, /* endTimeUs= */ C.TIME_UNSET);
@@ -93,7 +93,7 @@
DataSpec loadDataSpec = dataSpec.subrange(nextLoadPosition);
ExtractorInput input =
new DefaultExtractorInput(
- dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
+ dataSource, loadDataSpec.position, dataSource.open(loadDataSpec));
// Load and decode the initialization data.
try {
Extractor extractor = extractorWrapper.extractor;
@@ -103,7 +103,7 @@
}
Assertions.checkState(result != Extractor.RESULT_SEEK);
} finally {
- nextLoadPosition = input.getPosition() - dataSpec.absoluteStreamPosition;
+ nextLoadPosition = input.getPosition() - dataSpec.position;
}
} finally {
Util.closeQuietly(dataSource);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
index 00d841e..4e91e92 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
@@ -54,7 +55,7 @@
DataSpec dataSpec,
Format trackFormat,
int trackSelectionReason,
- Object trackSelectionData,
+ @Nullable Object trackSelectionData,
long startTimeUs,
long endTimeUs,
long chunkIndex,
@@ -90,7 +91,7 @@
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
BaseMediaChunkOutput output = getOutput();
output.setSampleOffsetUs(0);
TrackOutput trackOutput = output.track(0, trackType);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/package-info.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/package-info.java
new file mode 100644
index 0000000..adb05a4
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+package com.google.android.exoplayer2.source;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java
index 51aec36..830185c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java
@@ -15,13 +15,13 @@
*/
package com.google.android.exoplayer2.text;
-import android.annotation.TargetApi;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -124,7 +124,7 @@
* @param captionStyle A {@link CaptionStyle}.
* @return The equivalent {@link CaptionStyleCompat}.
*/
- @TargetApi(19)
+ @RequiresApi(19)
public static CaptionStyleCompat createFromCaptionStyle(
CaptioningManager.CaptionStyle captionStyle) {
if (Util.SDK_INT >= 21) {
@@ -159,7 +159,7 @@
this.typeface = typeface;
}
- @TargetApi(19)
+ @RequiresApi(19)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV19(
CaptioningManager.CaptionStyle captionStyle) {
@@ -168,7 +168,7 @@
captionStyle.edgeType, captionStyle.edgeColor, captionStyle.getTypeface());
}
- @TargetApi(21)
+ @RequiresApi(21)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV21(
CaptioningManager.CaptionStyle captionStyle) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java
index fa7f2cb..e63bf57 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java
@@ -145,27 +145,32 @@
/**
* The position of the {@link #lineAnchor} of the cue box within the viewport in the direction
- * orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of
- * the value depends on the value of {@link #lineType}.
- * <p>
- * For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the
- * fractional vertical position relative to the top of the viewport.
+ * orthogonal to the writing direction (determined by {@link #verticalType}), or {@link
+ * #DIMEN_UNSET}. When set, the interpretation of the value depends on the value of {@link
+ * #lineType}.
+ *
+ * <p>The measurement direction depends on {@link #verticalType}:
+ *
+ * <ul>
+ * <li>For {@link #TYPE_UNSET} (i.e. horizontal), this is the vertical position relative to the
+ * top of the viewport.
+ * <li>For {@link #VERTICAL_TYPE_LR} this is the horizontal position relative to the left of the
+ * viewport.
+ * <li>For {@link #VERTICAL_TYPE_RL} this is the horizontal position relative to the right of
+ * the viewport.
+ * </ul>
*/
public final float line;
/**
* The type of the {@link #line} value.
*
- * <p>{@link #LINE_TYPE_FRACTION} indicates that {@link #line} is a fractional position within the
- * viewport.
- *
* <ul>
* <li>{@link #LINE_TYPE_FRACTION} indicates that {@link #line} is a fractional position within
* the viewport.
- * <li>
+ * <li>{@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the size
+ * of each line is taken to be the size of the first line of the cue.
* <ul>
- * <li>{@link #LINE_TYPE_NUMBER} indicates that {@link #line} is a line number, where the
- * size of each line is taken to be the size of the first line of the cue.
* <li>When {@link #line} is greater than or equal to 0 lines count from the start of the
* viewport, with 0 indicating zero offset from the start edge. When {@link #line} is
* negative lines count from the end of the viewport, with -1 indicating zero offset
@@ -206,10 +211,16 @@
/**
* The fractional position of the {@link #positionAnchor} of the cue box within the viewport in
* the direction orthogonal to {@link #line}, or {@link #DIMEN_UNSET}.
- * <p>
- * For horizontal text, this is the horizontal position relative to the left of the viewport. Note
- * that positioning is relative to the left of the viewport even in the case of right-to-left
- * text.
+ *
+ * <p>The measurement direction depends on {@link #verticalType}.
+ *
+ * <ul>
+ * <li>For {@link #TYPE_UNSET} (i.e. horizontal), this is the horizontal position relative to
+ * the left of the viewport. Note that positioning is relative to the left of the viewport
+ * even in the case of right-to-left text.
+ * <li>For {@link #VERTICAL_TYPE_LR} and {@link #VERTICAL_TYPE_RL} (i.e. vertical), this is the
+ * vertical position relative to the top of the viewport.
+ * </ul>
*/
public final float position;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java
index 8a1aea1..7987c8b 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java
@@ -31,7 +31,7 @@
private final String name;
/** @param name The name of the decoder. */
- @SuppressWarnings("initialization:method.invocation.invalid")
+ @SuppressWarnings("nullness:method.invocation.invalid")
protected SimpleSubtitleDecoder(String name) {
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
this.name = name;
@@ -55,7 +55,7 @@
@Override
protected final SubtitleOutputBuffer createOutputBuffer() {
- return new SimpleSubtitleOutputBuffer(this);
+ return new SimpleSubtitleOutputBuffer(this::releaseOutputBuffer);
}
@Override
@@ -63,11 +63,6 @@
return new SubtitleDecoderException("Unexpected decode error", error);
}
- @Override
- protected final void releaseOutputBuffer(SubtitleOutputBuffer buffer) {
- super.releaseOutputBuffer(buffer);
- }
-
@SuppressWarnings("ByteBufferBackingArray")
@Override
@Nullable
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java
index b2c2563..6807661 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java
@@ -20,12 +20,10 @@
*/
/* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer {
- private final SimpleSubtitleDecoder owner;
+ private final Owner<SubtitleOutputBuffer> owner;
- /**
- * @param owner The decoder that owns this buffer.
- */
- public SimpleSubtitleOutputBuffer(SimpleSubtitleDecoder owner) {
+ /** @param owner The decoder that owns this buffer. */
+ public SimpleSubtitleOutputBuffer(Owner<SubtitleOutputBuffer> owner) {
super();
this.owner = owner;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SpanUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SpanUtil.java
deleted file mode 100644
index 9e9f350..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SpanUtil.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.text;
-
-import android.text.Spannable;
-import android.text.style.ForegroundColorSpan;
-
-/**
- * Utility methods for Android <a href="https://developer.android.com/guide/topics/text/spans">span
- * styling</a>.
- */
-public final class SpanUtil {
-
- /**
- * Adds {@code span} to {@code spannable} between {@code start} and {@code end}, removing any
- * existing spans of the same type and with the same indices and flags.
- *
- * <p>This is useful for types of spans that don't make sense to duplicate and where the
- * evaluation order might have an unexpected impact on the final text, e.g. {@link
- * ForegroundColorSpan}.
- *
- * @param spannable The {@link Spannable} to add {@code span} to.
- * @param span The span object to be added.
- * @param start The start index to add the new span at.
- * @param end The end index to add the new span at.
- * @param spanFlags The flags to pass to {@link Spannable#setSpan(Object, int, int, int)}.
- */
- public static void addOrReplaceSpan(
- Spannable spannable, Object span, int start, int end, int spanFlags) {
- Object[] existingSpans = spannable.getSpans(start, end, span.getClass());
- for (Object existingSpan : existingSpans) {
- if (spannable.getSpanStart(existingSpan) == start
- && spannable.getSpanEnd(existingSpan) == end
- && spannable.getSpanFlags(existingSpan) == spanFlags) {
- spannable.removeSpan(existingSpan);
- }
- }
- spannable.setSpan(span, start, end, spanFlags);
- }
-
- private SpanUtil() {}
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java
index 1dcdecf..63b997a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java
@@ -66,9 +66,6 @@
}
@Override
- public abstract void release();
-
- @Override
public void clear() {
super.clear();
subtitle = null;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java
index 058b1c4..b8b4d7d 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java
@@ -23,11 +23,12 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
@@ -45,6 +46,8 @@
*/
public final class TextRenderer extends BaseRenderer implements Callback {
+ private static final String TAG = "TextRenderer";
+
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -119,11 +122,16 @@
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
public int supportsFormat(Format format) {
if (decoderFactory.supportsFormat(format)) {
return RendererCapabilities.create(
- supportsFormatDrm(null, format.drmInitData) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_DRM);
+ format.drmInitData == null ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_DRM);
} else if (MimeTypes.isText(format.sampleMimeType)) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
} else {
@@ -143,19 +151,13 @@
@Override
protected void onPositionReset(long positionUs, boolean joining) {
- clearOutput();
inputStreamEnded = false;
outputStreamEnded = false;
- if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
- replaceDecoder();
- } else {
- releaseBuffers();
- decoder.flush();
- }
+ resetOutputAndDecoder();
}
@Override
- public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
+ public void render(long positionUs, long elapsedRealtimeUs) {
if (outputStreamEnded) {
return;
}
@@ -165,7 +167,8 @@
try {
nextSubtitle = decoder.dequeueOutputBuffer();
} catch (SubtitleDecoderException e) {
- throw createRendererException(e, streamFormat);
+ handleDecoderError(e);
+ return;
}
}
@@ -232,7 +235,7 @@
return;
}
// Try and read the next subtitle from the source.
- int result = readSource(formatHolder, nextInputBuffer, false);
+ @SampleStream.ReadDataResult int result = readSource(formatHolder, nextInputBuffer, false);
if (result == C.RESULT_BUFFER_READ) {
if (nextInputBuffer.isEndOfStream()) {
inputStreamEnded = true;
@@ -247,7 +250,8 @@
}
}
} catch (SubtitleDecoderException e) {
- throw createRendererException(e, streamFormat);
+ handleDecoderError(e);
+ return;
}
}
@@ -329,4 +333,24 @@
output.onCues(cues);
}
+ /**
+ * Called when {@link #decoder} throws an exception, so it can be logged and playback can
+ * continue.
+ *
+ * <p>Logs {@code e} and resets state to allow decoding the next sample.
+ */
+ private void handleDecoderError(SubtitleDecoderException e) {
+ Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e);
+ resetOutputAndDecoder();
+ }
+
+ private void resetOutputAndDecoder() {
+ clearOutput();
+ if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
+ replaceDecoder();
+ } else {
+ releaseBuffers();
+ decoder.flush();
+ }
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java
index e596f74..03a7255 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.text.cea;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@@ -45,6 +44,7 @@
private long playbackPositionUs;
private long queuedInputBufferCount;
+ @SuppressWarnings("nullness:methodref.receiver.bound.invalid")
public CeaDecoder() {
availableInputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
@@ -52,7 +52,7 @@
}
availableOutputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
- availableOutputBuffers.add(new CeaOutputBuffer());
+ availableOutputBuffers.add(new CeaOutputBuffer(this::releaseOutputBuffer));
}
queuedInputBuffers = new PriorityQueue<>();
}
@@ -185,7 +185,7 @@
private long queuedInputBufferCount;
@Override
- public int compareTo(@NonNull CeaInputBuffer other) {
+ public int compareTo(CeaInputBuffer other) {
if (isEndOfStream() != other.isEndOfStream()) {
return isEndOfStream() ? 1 : -1;
}
@@ -200,11 +200,17 @@
}
}
- private final class CeaOutputBuffer extends SubtitleOutputBuffer {
+ private static final class CeaOutputBuffer extends SubtitleOutputBuffer {
+
+ private Owner<CeaOutputBuffer> owner;
+
+ public CeaOutputBuffer(Owner<CeaOutputBuffer> owner) {
+ this.owner = owner;
+ }
@Override
public final void release() {
- releaseOutputBuffer(this);
+ owner.releaseOutputBuffer(this);
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/SpanUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/SpanUtil.java
new file mode 100644
index 0000000..d215f36
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/SpanUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.text.span;
+
+import android.text.Spannable;
+import android.text.style.ForegroundColorSpan;
+
+/**
+ * Utility methods for Android <a href="https://developer.android.com/guide/topics/text/spans">span
+ * styling</a>.
+ */
+public final class SpanUtil {
+
+ /**
+ * Adds {@code span} to {@code spannable} between {@code start} and {@code end}, removing any
+ * existing spans of the same type and with the same indices and flags.
+ *
+ * <p>This is useful for types of spans that don't make sense to duplicate and where the
+ * evaluation order might have an unexpected impact on the final text, e.g. {@link
+ * ForegroundColorSpan}.
+ *
+ * @param spannable The {@link Spannable} to add {@code span} to.
+ * @param span The span object to be added.
+ * @param start The start index to add the new span at.
+ * @param end The end index to add the new span at.
+ * @param spanFlags The flags to pass to {@link Spannable#setSpan(Object, int, int, int)}.
+ */
+ public static void addOrReplaceSpan(
+ Spannable spannable, Object span, int start, int end, int spanFlags) {
+ Object[] existingSpans = spannable.getSpans(start, end, span.getClass());
+ for (Object existingSpan : existingSpans) {
+ if (spannable.getSpanStart(existingSpan) == start
+ && spannable.getSpanEnd(existingSpan) == end
+ && spannable.getSpanFlags(existingSpan) == spanFlags) {
+ spannable.removeSpan(existingSpan);
+ }
+ }
+ spannable.setSpan(span, start, end, spanFlags);
+ }
+
+ private SpanUtil() {}
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/package-info.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/package-info.java
new file mode 100644
index 0000000..87876b1
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/span/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+package com.google.android.exoplayer2.text.span;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
index eef9d2e..b963b60 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaDecoder.java
@@ -139,7 +139,7 @@
* starts with {@code [} (i.e. the title of the next section).
*
* @param data A {@link ParsableByteArray} with {@link ParsableByteArray#getPosition() position}
- * set to the beginning of of the first line after {@code [Script Info]}.
+ * set to the beginning of the first line after {@code [Script Info]}.
*/
private void parseScriptInfo(ParsableByteArray data) {
@Nullable String currentLine;
@@ -175,7 +175,7 @@
* starts with {@code [} (i.e. the title of the next section).
*
* @param data A {@link ParsableByteArray} with {@link ParsableByteArray#getPosition()} pointing
- * at the beginning of of the first line after {@code [V4+ Styles]}.
+ * at the beginning of the first line after {@code [V4+ Styles]}.
*/
private static Map<String, SsaStyle> parseStyles(ParsableByteArray data) {
Map<String, SsaStyle> styles = new LinkedHashMap<>();
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java
index fd2cb03..0cba339 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ssa/SsaStyle.java
@@ -227,7 +227,7 @@
PointF position = null;
Matcher matcher = BRACES_PATTERN.matcher(text);
while (matcher.find()) {
- String braceContents = matcher.group(1);
+ String braceContents = Assertions.checkNotNull(matcher.group(1));
try {
PointF parsedPosition = parsePosition(braceContents);
if (parsedPosition != null) {
@@ -237,7 +237,8 @@
// Ignore invalid \pos() or \move() function.
}
try {
- @SsaAlignment int parsedAlignment = parseAlignmentOverride(braceContents);
+ @SsaAlignment
+ int parsedAlignment = parseAlignmentOverride(braceContents);
if (parsedAlignment != SSA_ALIGNMENT_UNKNOWN) {
alignment = parsedAlignment;
}
@@ -295,7 +296,9 @@
@SsaAlignment
private static int parseAlignmentOverride(String braceContents) {
Matcher matcher = ALIGNMENT_OVERRIDE_PATTERN.matcher(braceContents);
- return matcher.find() ? parseAlignment(matcher.group(1)) : SSA_ALIGNMENT_UNKNOWN;
+ return matcher.find()
+ ? parseAlignment(Assertions.checkNotNull(matcher.group(1)))
+ : SSA_ALIGNMENT_UNKNOWN;
}
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java
index 0c402ac..51f5973 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java
@@ -22,6 +22,7 @@
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
import com.google.android.exoplayer2.text.Subtitle;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.LongArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
@@ -41,10 +42,12 @@
private static final String TAG = "SubripDecoder";
- private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)";
+ // Some SRT files don't include hours or milliseconds in the timecode, so we use optional groups.
+ private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+)(?:,(\\d+))?";
private static final Pattern SUBRIP_TIMING_LINE =
Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")\\s*");
+ // NOTE: Android Studio's suggestion to simplify '\\}' is incorrect [internal: b/144480183].
private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}");
private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}";
@@ -229,10 +232,15 @@
}
private static long parseTimecode(Matcher matcher, int groupOffset) {
- long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000;
- timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000;
- timestampMs += Long.parseLong(matcher.group(groupOffset + 3)) * 1000;
- timestampMs += Long.parseLong(matcher.group(groupOffset + 4));
+ @Nullable String hours = matcher.group(groupOffset + 1);
+ long timestampMs = hours != null ? Long.parseLong(hours) * 60 * 60 * 1000 : 0;
+ timestampMs +=
+ Long.parseLong(Assertions.checkNotNull(matcher.group(groupOffset + 2))) * 60 * 1000;
+ timestampMs += Long.parseLong(Assertions.checkNotNull(matcher.group(groupOffset + 3))) * 1000;
+ @Nullable String millis = matcher.group(groupOffset + 4);
+ if (millis != null) {
+ timestampMs += Long.parseLong(millis);
+ }
return timestampMs * 1000;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/DeleteTextSpan.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/DeleteTextSpan.java
new file mode 100644
index 0000000..be41c39
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/DeleteTextSpan.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.text.ttml;
+
+import android.text.Spanned;
+
+/**
+ * A span used to mark a section of text for later deletion.
+ *
+ * <p>This is deliberately package-private because it's not generally supported by Android and
+ * results in surprising behaviour when simply calling {@link Spanned#toString} (i.e. the text isn't
+ * deleted).
+ *
+ * <p>This span is explicitly handled in {@code TtmlNode#cleanUpText}.
+ */
+/* package */ final class DeleteTextSpan {}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
index ff5bc21..80009d4 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
@@ -22,6 +22,7 @@
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
+import com.google.android.exoplayer2.text.span.RubySpan;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ColorParser;
import com.google.android.exoplayer2.util.Log;
@@ -524,6 +525,53 @@
break;
}
break;
+ case TtmlNode.ATTR_TTS_TEXT_COMBINE:
+ switch (Util.toLowerInvariant(attributeValue)) {
+ case TtmlNode.COMBINE_NONE:
+ style = createIfNull(style).setTextCombine(false);
+ break;
+ case TtmlNode.COMBINE_ALL:
+ style = createIfNull(style).setTextCombine(true);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ break;
+ case TtmlNode.ATTR_TTS_RUBY:
+ switch (Util.toLowerInvariant(attributeValue)) {
+ case TtmlNode.RUBY_CONTAINER:
+ style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_CONTAINER);
+ break;
+ case TtmlNode.RUBY_BASE:
+ case TtmlNode.RUBY_BASE_CONTAINER:
+ style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_BASE);
+ break;
+ case TtmlNode.RUBY_TEXT:
+ case TtmlNode.RUBY_TEXT_CONTAINER:
+ style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_TEXT);
+ break;
+ case TtmlNode.RUBY_DELIMITER:
+ style = createIfNull(style).setRubyType(TtmlStyle.RUBY_TYPE_DELIMITER);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ break;
+ case TtmlNode.ATTR_TTS_RUBY_POSITION:
+ switch (Util.toLowerInvariant(attributeValue)) {
+ case TtmlNode.RUBY_BEFORE:
+ style = createIfNull(style).setRubyPosition(RubySpan.POSITION_OVER);
+ break;
+ case TtmlNode.RUBY_AFTER:
+ style = createIfNull(style).setRubyPosition(RubySpan.POSITION_UNDER);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ break;
case TtmlNode.ATTR_TTS_TEXT_DECORATION:
switch (Util.toLowerInvariant(attributeValue)) {
case TtmlNode.LINETHROUGH:
@@ -637,8 +685,9 @@
endTime = parent.endTimeUs;
}
}
+
return TtmlNode.buildNode(
- parser.getName(), startTime, endTime, style, styleIds, regionId, imageId);
+ parser.getName(), startTime, endTime, style, styleIds, regionId, imageId, parent);
}
private static boolean isSupportedTag(String tag) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
index 2b2259d..c8e9ed7 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
@@ -64,10 +64,24 @@
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
public static final String ATTR_TTS_COLOR = "color";
+ public static final String ATTR_TTS_RUBY = "ruby";
+ public static final String ATTR_TTS_RUBY_POSITION = "rubyPosition";
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
+ public static final String ATTR_TTS_TEXT_COMBINE = "textCombine";
public static final String ATTR_TTS_WRITING_MODE = "writingMode";
+ // Values for ruby
+ public static final String RUBY_CONTAINER = "container";
+ public static final String RUBY_BASE = "base";
+ public static final String RUBY_BASE_CONTAINER = "baseContainer";
+ public static final String RUBY_TEXT = "text";
+ public static final String RUBY_TEXT_CONTAINER = "textContainer";
+ public static final String RUBY_DELIMITER = "delimiter";
+
+ // Values for rubyPosition
+ public static final String RUBY_BEFORE = "before";
+ public static final String RUBY_AFTER = "after";
// Values for textDecoration
public static final String LINETHROUGH = "linethrough";
public static final String NO_LINETHROUGH = "nolinethrough";
@@ -83,6 +97,10 @@
public static final String START = "start";
public static final String END = "end";
+ // Values for textCombine
+ public static final String COMBINE_NONE = "none";
+ public static final String COMBINE_ALL = "all";
+
// Values for writingMode
public static final String VERTICAL = "tb";
public static final String VERTICAL_LR = "tblr";
@@ -97,6 +115,7 @@
@Nullable private final String[] styleIds;
public final String regionId;
@Nullable public final String imageId;
+ @Nullable public final TtmlNode parent;
private final HashMap<String, Integer> nodeStartsByRegion;
private final HashMap<String, Integer> nodeEndsByRegion;
@@ -112,7 +131,8 @@
/* style= */ null,
/* styleIds= */ null,
ANONYMOUS_REGION_ID,
- /* imageId= */ null);
+ /* imageId= */ null,
+ /* parent= */ null);
}
public static TtmlNode buildNode(
@@ -122,9 +142,10 @@
@Nullable TtmlStyle style,
@Nullable String[] styleIds,
String regionId,
- @Nullable String imageId) {
+ @Nullable String imageId,
+ @Nullable TtmlNode parent) {
return new TtmlNode(
- tag, /* text= */ null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId);
+ tag, /* text= */ null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId, parent);
}
private TtmlNode(
@@ -135,7 +156,8 @@
@Nullable TtmlStyle style,
@Nullable String[] styleIds,
String regionId,
- @Nullable String imageId) {
+ @Nullable String imageId,
+ @Nullable TtmlNode parent) {
this.tag = tag;
this.text = text;
this.imageId = imageId;
@@ -145,6 +167,7 @@
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.regionId = Assertions.checkNotNull(regionId);
+ this.parent = parent;
nodeStartsByRegion = new HashMap<>();
nodeEndsByRegion = new HashMap<>();
}
@@ -356,14 +379,19 @@
regionOutput.setText(text);
}
if (resolvedStyle != null) {
- TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle);
+ TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent);
regionOutput.setVerticalType(resolvedStyle.getVerticalType());
}
}
private static void cleanUpText(SpannableStringBuilder builder) {
// Having joined the text elements, we need to do some final cleanup on the result.
- // 1. Collapse multiple consecutive spaces into a single space.
+ // Remove any text covered by a DeleteTextSpan (e.g. ruby text).
+ DeleteTextSpan[] deleteTextSpans = builder.getSpans(0, builder.length(), DeleteTextSpan.class);
+ for (DeleteTextSpan deleteTextSpan : deleteTextSpans) {
+ builder.replace(builder.getSpanStart(deleteTextSpan), builder.getSpanEnd(deleteTextSpan), "");
+ }
+ // Collapse multiple consecutive spaces into a single space.
for (int i = 0; i < builder.length(); i++) {
if (builder.charAt(i) == ' ') {
int j = i + 1;
@@ -376,7 +404,7 @@
}
}
}
- // 2. Remove any spaces from the start of each line.
+ // Remove any spaces from the start of each line.
if (builder.length() > 0 && builder.charAt(0) == ' ') {
builder.delete(0, 1);
}
@@ -385,7 +413,7 @@
builder.delete(i + 1, i + 2);
}
}
- // 3. Remove any spaces from the end of each line.
+ // Remove any spaces from the end of each line.
if (builder.length() > 0 && builder.charAt(builder.length() - 1) == ' ') {
builder.delete(builder.length() - 1, builder.length());
}
@@ -394,7 +422,7 @@
builder.delete(i, i + 1);
}
}
- // 4. Trim a trailing newline, if there is one.
+ // Trim a trailing newline, if there is one.
if (builder.length() > 0 && builder.charAt(builder.length() - 1) == '\n') {
builder.delete(builder.length() - 1, builder.length());
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
index e1f3d5e..e5ba2c9 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.text.ttml;
import android.text.Layout.Alignment;
+import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
@@ -28,7 +29,13 @@
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.text.SpanUtil;
+import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
+import com.google.android.exoplayer2.text.span.RubySpan;
+import com.google.android.exoplayer2.text.span.SpanUtil;
+import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayDeque;
+import java.util.Deque;
import java.util.Map;
/**
@@ -36,6 +43,8 @@
*/
/* package */ final class TtmlRenderUtil {
+ private static final String TAG = "TtmlRenderUtil";
+
@Nullable
public static TtmlStyle resolveStyle(
@Nullable TtmlStyle style, @Nullable String[] styleIds, Map<String, TtmlStyle> globalStyles) {
@@ -70,8 +79,8 @@
return style;
}
- public static void applyStylesToSpan(SpannableStringBuilder builder,
- int start, int end, TtmlStyle style) {
+ public static void applyStylesToSpan(
+ Spannable builder, int start, int end, TtmlStyle style, @Nullable TtmlNode parent) {
if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
builder.setSpan(new StyleSpan(style.getStyle()), start, end,
@@ -107,6 +116,53 @@
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ switch (style.getRubyType()) {
+ case TtmlStyle.RUBY_TYPE_BASE:
+ // look for the sibling RUBY_TEXT and add it as span between start & end.
+ @Nullable TtmlNode containerNode = findRubyContainerNode(parent);
+ if (containerNode == null) {
+ // No matching container node
+ break;
+ }
+ @Nullable TtmlNode textNode = findRubyTextNode(containerNode);
+ if (textNode == null) {
+ // no matching text node
+ break;
+ }
+ String rubyText;
+ if (textNode.getChildCount() == 1 && textNode.getChild(0).text != null) {
+ rubyText = Util.castNonNull(textNode.getChild(0).text);
+ } else {
+ Log.i(TAG, "Skipping rubyText node without exactly one text child.");
+ break;
+ }
+
+ // TODO: Get rubyPosition from `textNode` when TTML inheritance is implemented.
+ @RubySpan.Position
+ int rubyPosition =
+ containerNode.style != null
+ ? containerNode.style.getRubyPosition()
+ : RubySpan.POSITION_UNKNOWN;
+ builder.setSpan(
+ new RubySpan(rubyText, rubyPosition), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ break;
+ case TtmlStyle.RUBY_TYPE_DELIMITER:
+ // TODO: Add support for this when RubySpan supports parenthetical text. For now, just
+ // fall through and delete the text.
+ case TtmlStyle.RUBY_TYPE_TEXT:
+ // We can't just remove the text directly from `builder` here because TtmlNode has fixed
+ // ideas of where every node starts and ends (nodeStartsByRegion and nodeEndsByRegion) so
+ // all these indices become invalid if we mutate the underlying string at this point.
+ // Instead we add a special span that's then handled in TtmlNode#cleanUpText.
+ builder.setSpan(new DeleteTextSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ break;
+ case TtmlStyle.RUBY_TYPE_CONTAINER:
+ case TtmlStyle.UNSPECIFIED:
+ default:
+ // Do nothing
+ break;
+ }
+
@Nullable Alignment textAlign = style.getTextAlign();
if (textAlign != null) {
SpanUtil.addOrReplaceSpan(
@@ -116,6 +172,14 @@
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ if (style.getTextCombine()) {
+ SpanUtil.addOrReplaceSpan(
+ builder,
+ new HorizontalTextInVerticalContextSpan(),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
switch (style.getFontSizeUnit()) {
case TtmlStyle.FONT_SIZE_UNIT_PIXEL:
SpanUtil.addOrReplaceSpan(
@@ -147,6 +211,35 @@
}
}
+ @Nullable
+ private static TtmlNode findRubyTextNode(TtmlNode rubyContainerNode) {
+ Deque<TtmlNode> childNodesStack = new ArrayDeque<>();
+ childNodesStack.push(rubyContainerNode);
+ while (!childNodesStack.isEmpty()) {
+ TtmlNode childNode = childNodesStack.pop();
+ if (childNode.style != null && childNode.style.getRubyType() == TtmlStyle.RUBY_TYPE_TEXT) {
+ return childNode;
+ }
+ for (int i = childNode.getChildCount() - 1; i >= 0; i--) {
+ childNodesStack.push(childNode.getChild(i));
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static TtmlNode findRubyContainerNode(@Nullable TtmlNode node) {
+ while (node != null) {
+ @Nullable TtmlStyle style = node.style;
+ if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_CONTAINER) {
+ return node;
+ }
+ node = node.parent;
+ }
+ return null;
+ }
+
/**
* Called when the end of a paragraph is encountered. Adds a newline if there are one or more
* non-space characters since the previous newline.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
index b68b650..928af36 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
@@ -21,10 +21,10 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Cue.VerticalType;
+import com.google.android.exoplayer2.text.span.RubySpan;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Style object of a <code>TtmlNode</code>
@@ -62,7 +62,17 @@
private static final int OFF = 0;
private static final int ON = 1;
- private @MonotonicNonNull String fontFamily;
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({UNSPECIFIED, RUBY_TYPE_CONTAINER, RUBY_TYPE_BASE, RUBY_TYPE_TEXT, RUBY_TYPE_DELIMITER})
+ public @interface RubyType {}
+
+ public static final int RUBY_TYPE_CONTAINER = 1;
+ public static final int RUBY_TYPE_BASE = 2;
+ public static final int RUBY_TYPE_TEXT = 3;
+ public static final int RUBY_TYPE_DELIMITER = 4;
+
+ @Nullable private String fontFamily;
private int fontColor;
private boolean hasFontColor;
private int backgroundColor;
@@ -73,8 +83,11 @@
@OptionalBoolean private int italic;
@FontSizeUnit private int fontSizeUnit;
private float fontSize;
- private @MonotonicNonNull String id;
- private Layout.@MonotonicNonNull Alignment textAlign;
+ @Nullable private String id;
+ @RubyType private int rubyType;
+ @RubySpan.Position private int rubyPosition;
+ @Nullable private Layout.Alignment textAlign;
+ @OptionalBoolean private int textCombine;
@Cue.VerticalType private int verticalType;
public TtmlStyle() {
@@ -83,6 +96,9 @@
bold = UNSPECIFIED;
italic = UNSPECIFIED;
fontSizeUnit = UNSPECIFIED;
+ rubyType = UNSPECIFIED;
+ rubyPosition = RubySpan.POSITION_UNKNOWN;
+ textCombine = UNSPECIFIED;
verticalType = Cue.TYPE_UNSET;
}
@@ -133,7 +149,7 @@
return fontFamily;
}
- public TtmlStyle setFontFamily(String fontFamily) {
+ public TtmlStyle setFontFamily(@Nullable String fontFamily) {
this.fontFamily = fontFamily;
return this;
}
@@ -213,9 +229,15 @@
if (underline == UNSPECIFIED) {
underline = ancestor.underline;
}
+ if (rubyPosition == RubySpan.POSITION_UNKNOWN) {
+ rubyPosition = ancestor.rubyPosition;
+ }
if (textAlign == null && ancestor.textAlign != null) {
textAlign = ancestor.textAlign;
}
+ if (textCombine == UNSPECIFIED) {
+ textCombine = ancestor.textCombine;
+ }
if (fontSizeUnit == UNSPECIFIED) {
fontSizeUnit = ancestor.fontSizeUnit;
fontSize = ancestor.fontSize;
@@ -224,14 +246,17 @@
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
setBackgroundColor(ancestor.backgroundColor);
}
- if (chaining && verticalType != Cue.TYPE_UNSET && ancestor.verticalType == Cue.TYPE_UNSET) {
+ if (chaining && rubyType == UNSPECIFIED && ancestor.rubyType != UNSPECIFIED) {
+ rubyType = ancestor.rubyType;
+ }
+ if (chaining && verticalType == Cue.TYPE_UNSET && ancestor.verticalType != Cue.TYPE_UNSET) {
setVerticalType(ancestor.verticalType);
}
}
return this;
}
- public TtmlStyle setId(String id) {
+ public TtmlStyle setId(@Nullable String id) {
this.id = id;
return this;
}
@@ -241,16 +266,46 @@
return id;
}
+ public TtmlStyle setRubyType(@RubyType int rubyType) {
+ this.rubyType = rubyType;
+ return this;
+ }
+
+ @RubyType
+ public int getRubyType() {
+ return rubyType;
+ }
+
+ public TtmlStyle setRubyPosition(@RubySpan.Position int position) {
+ this.rubyPosition = position;
+ return this;
+ }
+
+ @RubySpan.Position
+ public int getRubyPosition() {
+ return rubyPosition;
+ }
+
@Nullable
public Layout.Alignment getTextAlign() {
return textAlign;
}
- public TtmlStyle setTextAlign(Layout.Alignment textAlign) {
+ public TtmlStyle setTextAlign(@Nullable Layout.Alignment textAlign) {
this.textAlign = textAlign;
return this;
}
+ /** Returns true if the source entity has {@code tts:textCombine=all}. */
+ public boolean getTextCombine() {
+ return textCombine == ON;
+ }
+
+ public TtmlStyle setTextCombine(boolean combine) {
+ this.textCombine = combine ? ON : OFF;
+ return this;
+ }
+
public TtmlStyle setFontSize(float fontSize) {
this.fontSize = fontSize;
return this;
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
index 7d5d51b..5efe378 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
@@ -17,6 +17,7 @@
import android.text.TextUtils;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ColorParser;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@@ -322,8 +323,8 @@
}
/**
- * Sets the target of a {@link WebvttCssStyle} by splitting a selector of the form
- * {@code ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional.
+ * Sets the target of a {@link WebvttCssStyle} by splitting a selector of the form {@code
+ * ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional.
*/
private void applySelectorToStyle(WebvttCssStyle style, String selector) {
if ("".equals(selector)) {
@@ -333,7 +334,7 @@
if (voiceStartIndex != -1) {
Matcher matcher = VOICE_NAME_PATTERN.matcher(selector.substring(voiceStartIndex));
if (matcher.matches()) {
- style.setTargetVoice(matcher.group(1));
+ style.setTargetVoice(Assertions.checkNotNull(matcher.group(1)));
}
selector = selector.substring(0, voiceStartIndex);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
index f62b073..7bd96b2 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
@@ -15,9 +15,10 @@
*/
package com.google.android.exoplayer2.text.webvtt;
-import static com.google.android.exoplayer2.text.SpanUtil.addOrReplaceSpan;
+import static com.google.android.exoplayer2.text.span.SpanUtil.addOrReplaceSpan;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.SpannableStringBuilder;
@@ -34,7 +35,6 @@
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
@@ -48,7 +48,10 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -138,6 +141,44 @@
private static final String TAG = "WebvttCueParser";
/**
+ * See WebVTT's <a href="https://www.w3.org/TR/webvtt1/#default-text-color">default text
+ * colors</a>.
+ */
+ private static final Map<String, Integer> DEFAULT_TEXT_COLORS;
+
+ static {
+ Map<String, Integer> defaultColors = new HashMap<>();
+ defaultColors.put("white", Color.rgb(255, 255, 255));
+ defaultColors.put("lime", Color.rgb(0, 255, 0));
+ defaultColors.put("cyan", Color.rgb(0, 255, 255));
+ defaultColors.put("red", Color.rgb(255, 0, 0));
+ defaultColors.put("yellow", Color.rgb(255, 255, 0));
+ defaultColors.put("magenta", Color.rgb(255, 0, 255));
+ defaultColors.put("blue", Color.rgb(0, 0, 255));
+ defaultColors.put("black", Color.rgb(0, 0, 0));
+ DEFAULT_TEXT_COLORS = Collections.unmodifiableMap(defaultColors);
+ }
+
+ /**
+ * See WebVTT's <a href="https://www.w3.org/TR/webvtt1/#default-text-background">default text
+ * background colors</a>.
+ */
+ private static final Map<String, Integer> DEFAULT_BACKGROUND_COLORS;
+
+ static {
+ Map<String, Integer> defaultBackgroundColors = new HashMap<>();
+ defaultBackgroundColors.put("bg_white", Color.rgb(255, 255, 255));
+ defaultBackgroundColors.put("bg_lime", Color.rgb(0, 255, 0));
+ defaultBackgroundColors.put("bg_cyan", Color.rgb(0, 255, 255));
+ defaultBackgroundColors.put("bg_red", Color.rgb(255, 0, 0));
+ defaultBackgroundColors.put("bg_yellow", Color.rgb(255, 255, 0));
+ defaultBackgroundColors.put("bg_magenta", Color.rgb(255, 0, 255));
+ defaultBackgroundColors.put("bg_blue", Color.rgb(0, 0, 255));
+ defaultBackgroundColors.put("bg_black", Color.rgb(0, 0, 0));
+ DEFAULT_BACKGROUND_COLORS = Collections.unmodifiableMap(defaultBackgroundColors);
+ }
+
+ /**
* Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text.
*
* @param webvttData Parsable WebVTT file data.
@@ -291,14 +332,16 @@
WebvttCueInfoBuilder builder = new WebvttCueInfoBuilder();
try {
// Parse the cue start and end times.
- builder.startTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1));
- builder.endTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2));
+ builder.startTimeUs =
+ WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(cueHeaderMatcher.group(1)));
+ builder.endTimeUs =
+ WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(cueHeaderMatcher.group(2)));
} catch (NumberFormatException e) {
Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group());
return null;
}
- parseCueSettingsList(cueHeaderMatcher.group(3), builder);
+ parseCueSettingsList(Assertions.checkNotNull(cueHeaderMatcher.group(3)), builder);
// Parse the cue text.
StringBuilder textBuilder = new StringBuilder();
@@ -319,8 +362,8 @@
Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList);
while (cueSettingMatcher.find()) {
- String name = cueSettingMatcher.group(1);
- String value = cueSettingMatcher.group(2);
+ String name = Assertions.checkNotNull(cueSettingMatcher.group(1));
+ String value = Assertions.checkNotNull(cueSettingMatcher.group(2));
try {
if ("line".equals(name)) {
parseLineAttribute(value, builder);
@@ -488,32 +531,14 @@
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case TAG_RUBY:
- @Nullable Element rubyTextElement = null;
- for (int i = 0; i < nestedElements.size(); i++) {
- if (TAG_RUBY_TEXT.equals(nestedElements.get(i).startTag.name)) {
- rubyTextElement = nestedElements.get(i);
- // Behaviour of multiple <rt> tags inside <ruby> is undefined, so use the first one.
- break;
- }
- }
- if (rubyTextElement == null) {
- break;
- }
- // Move the rubyText from spannedText into the RubySpan.
- CharSequence rubyText =
- text.subSequence(rubyTextElement.startTag.position, rubyTextElement.endPosition);
- text.delete(rubyTextElement.startTag.position, rubyTextElement.endPosition);
- end -= rubyText.length();
- text.setSpan(
- new RubySpan(rubyText.toString(), RubySpan.POSITION_OVER),
- start,
- end,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ applyRubySpans(nestedElements, text, start);
break;
case TAG_UNDERLINE:
text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case TAG_CLASS:
+ applyDefaultColors(text, startTag.classes, start, end);
+ break;
case TAG_LANG:
case TAG_VOICE:
case "": // Case of the "whole cue" virtual tag.
@@ -529,6 +554,54 @@
}
}
+ private static void applyRubySpans(
+ List<Element> nestedElements, SpannableStringBuilder text, int startTagPosition) {
+ List<Element> sortedNestedElements = new ArrayList<>(nestedElements.size());
+ sortedNestedElements.addAll(nestedElements);
+ Collections.sort(sortedNestedElements, Element.BY_START_POSITION_ASC);
+ int deletedCharCount = 0;
+ int lastRubyTextEnd = startTagPosition;
+ for (int i = 0; i < sortedNestedElements.size(); i++) {
+ if (!TAG_RUBY_TEXT.equals(sortedNestedElements.get(i).startTag.name)) {
+ continue;
+ }
+ Element rubyTextElement = sortedNestedElements.get(i);
+ // Move the rubyText from spannedText into the RubySpan.
+ int adjustedRubyTextStart = rubyTextElement.startTag.position - deletedCharCount;
+ int adjustedRubyTextEnd = rubyTextElement.endPosition - deletedCharCount;
+ CharSequence rubyText = text.subSequence(adjustedRubyTextStart, adjustedRubyTextEnd);
+ text.delete(adjustedRubyTextStart, adjustedRubyTextEnd);
+ text.setSpan(
+ new RubySpan(rubyText.toString(), RubySpan.POSITION_OVER),
+ lastRubyTextEnd,
+ adjustedRubyTextStart,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ deletedCharCount += rubyText.length();
+ // The ruby text has been deleted, so new-start == old-end.
+ lastRubyTextEnd = adjustedRubyTextStart;
+ }
+ }
+
+ /**
+ * Adds {@link ForegroundColorSpan}s and {@link BackgroundColorSpan}s to {@code text} for entries
+ * in {@code classes} that match WebVTT's <a
+ * href="https://www.w3.org/TR/webvtt1/#default-text-color">default text colors</a> or <a
+ * href="https://www.w3.org/TR/webvtt1/#default-text-background">default text background
+ * colors</a>.
+ */
+ private static void applyDefaultColors(
+ SpannableStringBuilder text, String[] classes, int start, int end) {
+ for (String className : classes) {
+ if (DEFAULT_TEXT_COLORS.containsKey(className)) {
+ int color = DEFAULT_TEXT_COLORS.get(className);
+ text.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else if (DEFAULT_BACKGROUND_COLORS.containsKey(className)) {
+ int color = DEFAULT_BACKGROUND_COLORS.get(className);
+ text.setSpan(new BackgroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ }
+
private static void applyStyleToText(SpannableStringBuilder spannedText, WebvttCssStyle style,
int start, int end) {
if (style == null) {
@@ -803,7 +876,7 @@
}
@Override
- public int compareTo(@NonNull StyleMatch another) {
+ public int compareTo(StyleMatch another) {
return this.score - another.score;
}
@@ -855,6 +928,9 @@
/** Information about a complete element (i.e. start tag and end position). */
private static class Element {
+ private static final Comparator<Element> BY_START_POSITION_ASC =
+ (e1, e2) -> Integer.compare(e1.startTag.position, e2.startTag.position);
+
private final StartTag startTag;
/**
* The position of the end of this element's text in the un-marked-up cue text (i.e. the
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
index 822fd03..5596031 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
@@ -550,23 +550,6 @@
}
/**
- * @deprecated Use {@link #setAllowVideoMixedMimeTypeAdaptiveness(boolean)} and {@link
- * #setAllowAudioMixedMimeTypeAdaptiveness(boolean)}.
- */
- @Deprecated
- public ParametersBuilder setAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) {
- setAllowAudioMixedMimeTypeAdaptiveness(allowMixedMimeAdaptiveness);
- setAllowVideoMixedMimeTypeAdaptiveness(allowMixedMimeAdaptiveness);
- return this;
- }
-
- /** @deprecated Use {@link #setAllowVideoNonSeamlessAdaptiveness(boolean)} */
- @Deprecated
- public ParametersBuilder setAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) {
- return setAllowVideoNonSeamlessAdaptiveness(allowNonSeamlessAdaptiveness);
- }
-
- /**
* Sets whether to exceed renderer capabilities when no selection can be made otherwise.
*
* <p>This parameter applies when all of the tracks available for a renderer exceed the
@@ -819,19 +802,6 @@
@SuppressWarnings("deprecation")
public static final Parameters DEFAULT_WITHOUT_CONTEXT = new ParametersBuilder().build();
- /**
- * @deprecated This instance does not have {@link Context} constraints configured. Use {@link
- * #getDefaults(Context)} instead.
- */
- @Deprecated public static final Parameters DEFAULT_WITHOUT_VIEWPORT = DEFAULT_WITHOUT_CONTEXT;
-
- /**
- * @deprecated This instance does not have {@link Context} constraints configured. Use {@link
- * #getDefaults(Context)} instead.
- */
- @Deprecated
- public static final Parameters DEFAULT = DEFAULT_WITHOUT_CONTEXT;
-
/** Returns an instance configured with default values. */
public static Parameters getDefaults(Context context) {
return new ParametersBuilder(context).build();
@@ -947,13 +917,6 @@
*/
public final boolean forceHighestSupportedBitrate;
/**
- * @deprecated Use {@link #allowVideoMixedMimeTypeAdaptiveness} and {@link
- * #allowAudioMixedMimeTypeAdaptiveness}.
- */
- @Deprecated public final boolean allowMixedMimeAdaptiveness;
- /** @deprecated Use {@link #allowVideoNonSeamlessAdaptiveness}. */
- @Deprecated public final boolean allowNonSeamlessAdaptiveness;
- /**
* Whether to exceed renderer capabilities when no selection can be made otherwise.
*
* <p>This parameter applies when all of the tracks available for a renderer exceed the
@@ -1037,9 +1000,6 @@
this.forceHighestSupportedBitrate = forceHighestSupportedBitrate;
this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary;
this.tunnelingAudioSessionId = tunnelingAudioSessionId;
- // Deprecated fields.
- this.allowMixedMimeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness;
- this.allowNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness;
// Overrides
this.selectionOverrides = selectionOverrides;
this.rendererDisabledFlags = rendererDisabledFlags;
@@ -1074,9 +1034,6 @@
// Overrides
this.selectionOverrides = readSelectionOverrides(in);
this.rendererDisabledFlags = Util.castNonNull(in.readSparseBooleanArray());
- // Deprecated fields.
- this.allowMixedMimeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness;
- this.allowNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness;
}
/**
@@ -1537,65 +1494,6 @@
return getParameters().buildUpon();
}
- /** @deprecated Use {@link ParametersBuilder#setRendererDisabled(int, boolean)}. */
- @Deprecated
- public final void setRendererDisabled(int rendererIndex, boolean disabled) {
- setParameters(buildUponParameters().setRendererDisabled(rendererIndex, disabled));
- }
-
- /** @deprecated Use {@link Parameters#getRendererDisabled(int)}. */
- @Deprecated
- public final boolean getRendererDisabled(int rendererIndex) {
- return getParameters().getRendererDisabled(rendererIndex);
- }
-
- /**
- * @deprecated Use {@link ParametersBuilder#setSelectionOverride(int, TrackGroupArray,
- * SelectionOverride)}.
- */
- @Deprecated
- public final void setSelectionOverride(
- int rendererIndex, TrackGroupArray groups, @Nullable SelectionOverride override) {
- setParameters(buildUponParameters().setSelectionOverride(rendererIndex, groups, override));
- }
-
- /** @deprecated Use {@link Parameters#hasSelectionOverride(int, TrackGroupArray)}. */
- @Deprecated
- public final boolean hasSelectionOverride(int rendererIndex, TrackGroupArray groups) {
- return getParameters().hasSelectionOverride(rendererIndex, groups);
- }
-
- /** @deprecated Use {@link Parameters#getSelectionOverride(int, TrackGroupArray)}. */
- @Deprecated
- @Nullable
- public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) {
- return getParameters().getSelectionOverride(rendererIndex, groups);
- }
-
- /** @deprecated Use {@link ParametersBuilder#clearSelectionOverride(int, TrackGroupArray)}. */
- @Deprecated
- public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) {
- setParameters(buildUponParameters().clearSelectionOverride(rendererIndex, groups));
- }
-
- /** @deprecated Use {@link ParametersBuilder#clearSelectionOverrides(int)}. */
- @Deprecated
- public final void clearSelectionOverrides(int rendererIndex) {
- setParameters(buildUponParameters().clearSelectionOverrides(rendererIndex));
- }
-
- /** @deprecated Use {@link ParametersBuilder#clearSelectionOverrides()}. */
- @Deprecated
- public final void clearSelectionOverrides() {
- setParameters(buildUponParameters().clearSelectionOverrides());
- }
-
- /** @deprecated Use {@link ParametersBuilder#setTunnelingAudioSessionId(int)}. */
- @Deprecated
- public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) {
- setParameters(buildUponParameters().setTunnelingAudioSessionId(tunnelingAudioSessionId));
- }
-
/**
* Allows the creation of multiple adaptive track selections.
*
@@ -2133,11 +2031,12 @@
getAdaptiveAudioTracks(
selectedGroup,
formatSupports[selectedGroupIndex],
+ selectedTrackIndex,
params.maxAudioBitrate,
params.allowAudioMixedMimeTypeAdaptiveness,
params.allowAudioMixedSampleRateAdaptiveness,
params.allowAudioMixedChannelCountAdaptiveness);
- if (adaptiveTracks.length > 0) {
+ if (adaptiveTracks.length > 1) {
definition = new TrackSelection.Definition(selectedGroup, adaptiveTracks);
}
}
@@ -2152,100 +2051,49 @@
private static int[] getAdaptiveAudioTracks(
TrackGroup group,
@Capabilities int[] formatSupport,
+ int primaryTrackIndex,
int maxAudioBitrate,
boolean allowMixedMimeTypeAdaptiveness,
boolean allowMixedSampleRateAdaptiveness,
boolean allowAudioMixedChannelCountAdaptiveness) {
- int selectedConfigurationTrackCount = 0;
- AudioConfigurationTuple selectedConfiguration = null;
- HashSet<AudioConfigurationTuple> seenConfigurationTuples = new HashSet<>();
- for (int i = 0; i < group.length; i++) {
- Format format = group.getFormat(i);
- AudioConfigurationTuple configuration =
- new AudioConfigurationTuple(
- format.channelCount, format.sampleRate, format.sampleMimeType);
- if (seenConfigurationTuples.add(configuration)) {
- int configurationCount =
- getAdaptiveAudioTrackCount(
- group,
- formatSupport,
- configuration,
- maxAudioBitrate,
- allowMixedMimeTypeAdaptiveness,
- allowMixedSampleRateAdaptiveness,
- allowAudioMixedChannelCountAdaptiveness);
- if (configurationCount > selectedConfigurationTrackCount) {
- selectedConfiguration = configuration;
- selectedConfigurationTrackCount = configurationCount;
- }
- }
- }
-
- if (selectedConfigurationTrackCount > 1) {
- Assertions.checkNotNull(selectedConfiguration);
- int[] adaptiveIndices = new int[selectedConfigurationTrackCount];
- int index = 0;
- for (int i = 0; i < group.length; i++) {
- Format format = group.getFormat(i);
- if (isSupportedAdaptiveAudioTrack(
- format,
- formatSupport[i],
- selectedConfiguration,
- maxAudioBitrate,
- allowMixedMimeTypeAdaptiveness,
- allowMixedSampleRateAdaptiveness,
- allowAudioMixedChannelCountAdaptiveness)) {
- adaptiveIndices[index++] = i;
- }
- }
- return adaptiveIndices;
- }
- return NO_TRACKS;
- }
-
- private static int getAdaptiveAudioTrackCount(
- TrackGroup group,
- @Capabilities int[] formatSupport,
- AudioConfigurationTuple configuration,
- int maxAudioBitrate,
- boolean allowMixedMimeTypeAdaptiveness,
- boolean allowMixedSampleRateAdaptiveness,
- boolean allowAudioMixedChannelCountAdaptiveness) {
+ Format primaryFormat = group.getFormat(primaryTrackIndex);
+ int[] adaptiveIndices = new int[group.length];
int count = 0;
for (int i = 0; i < group.length; i++) {
- if (isSupportedAdaptiveAudioTrack(
- group.getFormat(i),
- formatSupport[i],
- configuration,
- maxAudioBitrate,
- allowMixedMimeTypeAdaptiveness,
- allowMixedSampleRateAdaptiveness,
- allowAudioMixedChannelCountAdaptiveness)) {
- count++;
+ if (i == primaryTrackIndex
+ || isSupportedAdaptiveAudioTrack(
+ group.getFormat(i),
+ formatSupport[i],
+ primaryFormat,
+ maxAudioBitrate,
+ allowMixedMimeTypeAdaptiveness,
+ allowMixedSampleRateAdaptiveness,
+ allowAudioMixedChannelCountAdaptiveness)) {
+ adaptiveIndices[count++] = i;
}
}
- return count;
+ return Arrays.copyOf(adaptiveIndices, count);
}
private static boolean isSupportedAdaptiveAudioTrack(
Format format,
@Capabilities int formatSupport,
- AudioConfigurationTuple configuration,
+ Format primaryFormat,
int maxAudioBitrate,
boolean allowMixedMimeTypeAdaptiveness,
boolean allowMixedSampleRateAdaptiveness,
boolean allowAudioMixedChannelCountAdaptiveness) {
- return isSupported(formatSupport, false)
+ return isSupported(formatSupport, /* allowExceedsCapabilities= */ false)
&& (format.bitrate == Format.NO_VALUE || format.bitrate <= maxAudioBitrate)
&& (allowAudioMixedChannelCountAdaptiveness
|| (format.channelCount != Format.NO_VALUE
- && format.channelCount == configuration.channelCount))
+ && format.channelCount == primaryFormat.channelCount))
&& (allowMixedMimeTypeAdaptiveness
|| (format.sampleMimeType != null
- && TextUtils.equals(format.sampleMimeType, configuration.mimeType)))
+ && TextUtils.equals(format.sampleMimeType, primaryFormat.sampleMimeType)))
&& (allowMixedSampleRateAdaptiveness
|| (format.sampleRate != Format.NO_VALUE
- && format.sampleRate == configuration.sampleRate));
+ && format.sampleRate == primaryFormat.sampleRate));
}
// Text track selection implementation.
@@ -2705,41 +2553,6 @@
}
}
- private static final class AudioConfigurationTuple {
-
- public final int channelCount;
- public final int sampleRate;
- @Nullable public final String mimeType;
-
- public AudioConfigurationTuple(int channelCount, int sampleRate, @Nullable String mimeType) {
- this.channelCount = channelCount;
- this.sampleRate = sampleRate;
- this.mimeType = mimeType;
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- AudioConfigurationTuple other = (AudioConfigurationTuple) obj;
- return channelCount == other.channelCount && sampleRate == other.sampleRate
- && TextUtils.equals(mimeType, other.mimeType);
- }
-
- @Override
- public int hashCode() {
- int result = channelCount;
- result = 31 * result + sampleRate;
- result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
- return result;
- }
-
- }
-
/** Represents how well a text track matches the selection {@link Parameters}. */
protected static final class TextTrackScore implements Comparable<TextTrackScore> {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java
index f6ba1f2..59d50af 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java
@@ -92,6 +92,7 @@
@Deprecated public final int length;
private final int rendererCount;
+ private final String[] rendererNames;
private final int[] rendererTrackTypes;
private final TrackGroupArray[] rendererTrackGroups;
@AdaptiveSupport private final int[] rendererMixedMimeTypeAdaptiveSupports;
@@ -99,6 +100,7 @@
private final TrackGroupArray unmappedTrackGroups;
/**
+ * @param rendererNames The name of each renderer.
* @param rendererTrackTypes The track type handled by each renderer.
* @param rendererTrackGroups The {@link TrackGroup}s mapped to each renderer.
* @param rendererMixedMimeTypeAdaptiveSupports The {@link AdaptiveSupport} for mixed MIME type
@@ -109,11 +111,13 @@
*/
@SuppressWarnings("deprecation")
/* package */ MappedTrackInfo(
+ String[] rendererNames,
int[] rendererTrackTypes,
TrackGroupArray[] rendererTrackGroups,
@AdaptiveSupport int[] rendererMixedMimeTypeAdaptiveSupports,
@Capabilities int[][][] rendererFormatSupports,
TrackGroupArray unmappedTrackGroups) {
+ this.rendererNames = rendererNames;
this.rendererTrackTypes = rendererTrackTypes;
this.rendererTrackGroups = rendererTrackGroups;
this.rendererFormatSupports = rendererFormatSupports;
@@ -129,6 +133,17 @@
}
/**
+ * Returns the name of the renderer at a given index.
+ *
+ * @see Renderer#getName()
+ * @param rendererIndex The renderer index.
+ * @return The name of the renderer.
+ */
+ public String getRendererName(int rendererIndex) {
+ return rendererNames[rendererIndex];
+ }
+
+ /**
* Returns the track type that the renderer at a given index handles.
*
* @see Renderer#getTrackType()
@@ -380,6 +395,7 @@
// Create a track group array for each renderer, and trim each rendererFormatSupports entry.
TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length];
+ String[] rendererNames = new String[rendererCapabilities.length];
int[] rendererTrackTypes = new int[rendererCapabilities.length];
for (int i = 0; i < rendererCapabilities.length; i++) {
int rendererTrackGroupCount = rendererTrackGroupCounts[i];
@@ -388,6 +404,7 @@
Util.nullSafeArrayCopy(rendererTrackGroups[i], rendererTrackGroupCount));
rendererFormatSupports[i] =
Util.nullSafeArrayCopy(rendererFormatSupports[i], rendererTrackGroupCount);
+ rendererNames[i] = rendererCapabilities[i].getName();
rendererTrackTypes[i] = rendererCapabilities[i].getTrackType();
}
@@ -401,6 +418,7 @@
// Package up the track information and selections.
MappedTrackInfo mappedTrackInfo =
new MappedTrackInfo(
+ rendererNames,
rendererTrackTypes,
rendererTrackGroupArrays,
rendererMixedMimeTypeAdaptationSupports,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java
index 6e10171..3871a31 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.trackselection;
-import android.annotation.TargetApi;
import android.content.Context;
import android.os.Looper;
import android.os.Parcel;
@@ -23,6 +22,7 @@
import android.text.TextUtils;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.util.Locale;
@@ -47,7 +47,7 @@
*
* @param context Any context.
*/
- @SuppressWarnings({"deprecation", "initialization:method.invocation.invalid"})
+ @SuppressWarnings({"deprecation", "nullness:method.invocation.invalid"})
public Builder(Context context) {
this();
setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(context);
@@ -169,7 +169,7 @@
disabledTextTrackSelectionFlags);
}
- @TargetApi(19)
+ @RequiresApi(19)
private void setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettingsV19(
Context context) {
if (Util.SDK_INT < 23 && Looper.myLooper() == null) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
index f2cf344..17f8427 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
@@ -279,8 +279,8 @@
try {
connection = makeConnection(dataSpec);
} catch (IOException e) {
- throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
- dataSpec, HttpDataSourceException.TYPE_OPEN);
+ throw new HttpDataSourceException(
+ "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
String responseMessage;
@@ -289,8 +289,8 @@
responseMessage = connection.getResponseMessage();
} catch (IOException e) {
closeConnectionQuietly();
- throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
- dataSpec, HttpDataSourceException.TYPE_OPEN);
+ throw new HttpDataSourceException(
+ "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
// Check for a valid response code.
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java
index 68ab2a7..37922db 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java
@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.upstream;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.LoadEventInfo;
+import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.upstream.Loader.Callback;
import com.google.android.exoplayer2.upstream.Loader.Loadable;
import java.io.IOException;
@@ -41,22 +43,23 @@
/** Holds information about a load task error. */
final class LoadErrorInfo {
- /** One of the {@link C C.DATA_TYPE_*} constants indicating the type of data to load. */
- public final int dataType;
- /**
- * The duration in milliseconds of the load from the start of the first load attempt up to the
- * point at which the error occurred.
- */
- public final long loadDurationMs;
+ /** The {@link LoadEventInfo} associated with the load that encountered an error. */
+ public final LoadEventInfo loadEventInfo;
+ /** {@link MediaLoadData} associated with the load that encountered an error. */
+ public final MediaLoadData mediaLoadData;
/** The exception associated to the load error. */
public final IOException exception;
/** The number of errors this load task has encountered, including this one. */
public final int errorCount;
/** Creates an instance with the given values. */
- public LoadErrorInfo(int dataType, long loadDurationMs, IOException exception, int errorCount) {
- this.dataType = dataType;
- this.loadDurationMs = loadDurationMs;
+ public LoadErrorInfo(
+ LoadEventInfo loadEventInfo,
+ MediaLoadData mediaLoadData,
+ IOException exception,
+ int errorCount) {
+ this.loadEventInfo = loadEventInfo;
+ this.mediaLoadData = mediaLoadData;
this.exception = exception;
this.errorCount = errorCount;
}
@@ -88,8 +91,8 @@
*/
default long getBlacklistDurationMsFor(LoadErrorInfo loadErrorInfo) {
return getBlacklistDurationMsFor(
- loadErrorInfo.dataType,
- loadErrorInfo.loadDurationMs,
+ loadErrorInfo.mediaLoadData.dataType,
+ loadErrorInfo.loadEventInfo.loadDurationMs,
loadErrorInfo.exception,
loadErrorInfo.errorCount);
}
@@ -127,8 +130,8 @@
*/
default long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
return getRetryDelayMsFor(
- loadErrorInfo.dataType,
- loadErrorInfo.loadDurationMs,
+ loadErrorInfo.mediaLoadData.dataType,
+ loadErrorInfo.loadEventInfo.loadDurationMs,
loadErrorInfo.exception,
loadErrorInfo.errorCount);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java
index d9d84bd..4ff58b1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java
@@ -63,10 +63,8 @@
* Performs the load, returning on completion or cancellation.
*
* @throws IOException If the input could not be loaded.
- * @throws InterruptedException If the thread was interrupted.
*/
- void load() throws IOException, InterruptedException;
-
+ void load() throws IOException;
}
/**
@@ -400,12 +398,6 @@
if (!released) {
obtainMessage(MSG_IO_EXCEPTION, e).sendToTarget();
}
- } catch (InterruptedException e) {
- // The load was canceled.
- Assertions.checkState(canceled);
- if (!released) {
- sendEmptyMessage(MSG_END_OF_SOURCE);
- }
} catch (Exception e) {
// This should never happen, but handle it anyway.
Log.e(TAG, "Unexpected exception loading stream", e);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java
index 7fceda1..a60d35e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java
@@ -109,7 +109,11 @@
* @param parser Parses the object from the response.
*/
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<? extends T> parser) {
- this(dataSource, new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP), type, parser);
+ this(
+ dataSource,
+ new DataSpec.Builder().setUri(uri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build(),
+ type,
+ parser);
}
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java
index 7e5a274..f5fb67e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/ResolvingDataSource.java
@@ -110,8 +110,8 @@
return upstreamDataSource.read(buffer, offset, readLength);
}
- @Nullable
@Override
+ @Nullable
public Uri getUri() {
@Nullable Uri reportedUri = upstreamDataSource.getUri();
return reportedUri == null ? null : resolver.resolveReportedUri(reportedUri);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
index fdddcf9..92dff8b 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
@@ -169,9 +169,7 @@
dataSpec.length == C.LENGTH_UNSET
? C.LENGTH_UNSET
: Math.min(dataSpec.length - dataSpecBytesWritten, dataSpecFragmentSize);
- file =
- cache.startFile(
- dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, length);
+ file = cache.startFile(dataSpec.key, dataSpec.position + dataSpecBytesWritten, length);
FileOutputStream underlyingFileOutputStream = new FileOutputStream(file);
if (bufferSize > 0) {
if (bufferedOutputStream == null) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
index 502a22a..5142f24 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java
@@ -23,7 +23,6 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
-import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TeeDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
@@ -132,15 +131,10 @@
private final boolean ignoreCacheOnError;
private final boolean ignoreCacheForUnsetLengthRequests;
+ @Nullable private Uri actualUri;
+ @Nullable private DataSpec requestDataSpec;
@Nullable private DataSource currentDataSource;
private boolean currentDataSpecLengthUnset;
- @Nullable private Uri uri;
- @Nullable private Uri actualUri;
- @HttpMethod private int httpMethod;
- @Nullable private byte[] httpBody;
- private Map<String, String> httpRequestHeaders = Collections.emptyMap();
- @DataSpec.Flags private int flags;
- @Nullable private String key;
private long readPosition;
private long bytesRemaining;
@Nullable private CacheSpan currentHoleSpan;
@@ -259,13 +253,9 @@
@Override
public long open(DataSpec dataSpec) throws IOException {
try {
- key = cacheKeyFactory.buildCacheKey(dataSpec);
- uri = dataSpec.uri;
- actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ uri);
- httpMethod = dataSpec.httpMethod;
- httpBody = dataSpec.httpBody;
- httpRequestHeaders = dataSpec.httpRequestHeaders;
- flags = dataSpec.flags;
+ String key = cacheKeyFactory.buildCacheKey(dataSpec);
+ requestDataSpec = dataSpec.buildUpon().setKey(key).build();
+ actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ requestDataSpec.uri);
readPosition = dataSpec.position;
int reason = shouldIgnoreCacheForRequest(dataSpec);
@@ -351,14 +341,9 @@
@Override
public void close() throws IOException {
- uri = null;
+ requestDataSpec = null;
actualUri = null;
- httpMethod = DataSpec.HTTP_METHOD_GET;
- httpBody = null;
- httpRequestHeaders = Collections.emptyMap();
- flags = 0;
readPosition = 0;
- key = null;
notifyBytesRead();
try {
closeCurrentSource();
@@ -384,6 +369,7 @@
*/
private void openNextSource(boolean checkCache) throws IOException {
@Nullable CacheSpan nextSpan;
+ String key = requestDataSpec.key;
if (currentRequestIgnoresCache) {
nextSpan = null;
} else if (blockOnCache) {
@@ -404,27 +390,24 @@
// from upstream.
nextDataSource = upstreamDataSource;
nextDataSpec =
- new DataSpec(
- uri,
- httpMethod,
- httpBody,
- readPosition,
- readPosition,
- bytesRemaining,
- key,
- flags,
- httpRequestHeaders);
+ requestDataSpec.buildUpon().setPosition(readPosition).setLength(bytesRemaining).build();
} else if (nextSpan.isCached) {
- // Data is cached, read from cache.
+ // Data is cached in a span file starting at nextSpan.position.
Uri fileUri = Uri.fromFile(nextSpan.file);
- long filePosition = readPosition - nextSpan.position;
- long length = nextSpan.length - filePosition;
+ long filePositionOffset = nextSpan.position;
+ long positionInFile = readPosition - filePositionOffset;
+ long length = nextSpan.length - positionInFile;
if (bytesRemaining != C.LENGTH_UNSET) {
length = Math.min(length, bytesRemaining);
}
- // Deliberately skip the HTTP-related parameters since we're reading from the cache, not
- // making an HTTP request.
- nextDataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags);
+ nextDataSpec =
+ requestDataSpec
+ .buildUpon()
+ .setUri(fileUri)
+ .setUriPositionOffset(filePositionOffset)
+ .setPosition(positionInFile)
+ .setLength(length)
+ .build();
nextDataSource = cacheReadDataSource;
} else {
// Data is not cached, and data is not locked, read from upstream with cache backing.
@@ -438,16 +421,7 @@
}
}
nextDataSpec =
- new DataSpec(
- uri,
- httpMethod,
- httpBody,
- readPosition,
- readPosition,
- length,
- key,
- flags,
- httpRequestHeaders);
+ requestDataSpec.buildUpon().setPosition(readPosition).setLength(length).build();
if (cacheWriteDataSource != null) {
nextDataSource = cacheWriteDataSource;
} else {
@@ -494,7 +468,7 @@
}
if (isReadingFromUpstream()) {
actualUri = currentDataSource.getUri();
- boolean isRedirected = !uri.equals(actualUri);
+ boolean isRedirected = !requestDataSpec.uri.equals(actualUri);
ContentMetadataMutations.setRedirectedUri(mutations, isRedirected ? actualUri : null);
}
if (isWritingToCache()) {
@@ -507,7 +481,7 @@
if (isWritingToCache()) {
ContentMetadataMutations mutations = new ContentMetadataMutations();
ContentMetadataMutations.setContentLength(mutations, readPosition);
- cache.applyContentMetadataMutations(key, mutations);
+ cache.applyContentMetadataMutations(requestDataSpec.key, mutations);
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java
index 609e933..bf51a69 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.upstream.cache;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.File;
@@ -95,7 +94,7 @@
}
@Override
- public int compareTo(@NonNull CacheSpan another) {
+ public int compareTo(CacheSpan another) {
if (!key.equals(another.key)) {
return key.compareTo(another.key);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
index fadffd9..8da2fb1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java
@@ -79,7 +79,7 @@
public static Pair<Long, Long> getCached(
DataSpec dataSpec, Cache cache, @Nullable CacheKeyFactory cacheKeyFactory) {
String key = buildCacheKey(dataSpec, cacheKeyFactory);
- long position = dataSpec.absoluteStreamPosition;
+ long position = dataSpec.position;
long requestLength = getRequestLength(dataSpec, cache, key);
long bytesAlreadyCached = 0;
long bytesLeft = requestLength;
@@ -193,7 +193,7 @@
bytesLeft = getRequestLength(dataSpec, cache, key);
}
- long position = dataSpec.absoluteStreamPosition;
+ long position = dataSpec.position;
boolean lengthUnset = bytesLeft == C.LENGTH_UNSET;
while (bytesLeft != 0) {
throwExceptionIfInterruptedOrCancelled(isCanceled);
@@ -238,18 +238,16 @@
return dataSpec.length;
} else {
long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(key));
- return contentLength == C.LENGTH_UNSET
- ? C.LENGTH_UNSET
- : contentLength - dataSpec.absoluteStreamPosition;
+ return contentLength == C.LENGTH_UNSET ? C.LENGTH_UNSET : contentLength - dataSpec.position;
}
}
/**
* Reads and discards all data specified by the {@code dataSpec}.
*
- * @param dataSpec Defines the data to be read. {@code absoluteStreamPosition} and {@code length}
- * fields are overwritten by the following parameters.
- * @param absoluteStreamPosition The absolute position of the data to be read.
+ * @param dataSpec Defines the data to be read. The {@code position} and {@code length} fields are
+ * overwritten by the following parameters.
+ * @param position The position of the data to be read.
* @param length Length of the data to be read, or {@link C#LENGTH_UNSET} if it is unknown.
* @param dataSource The {@link DataSource} to read the data from.
* @param buffer The buffer to be used while downloading.
@@ -264,7 +262,7 @@
*/
private static long readAndDiscard(
DataSpec dataSpec,
- long absoluteStreamPosition,
+ long position,
long length,
DataSource dataSource,
byte[] buffer,
@@ -274,7 +272,7 @@
boolean isLastBlock,
@Nullable AtomicBoolean isCanceled)
throws IOException, InterruptedException {
- long positionOffset = absoluteStreamPosition - dataSpec.absoluteStreamPosition;
+ long positionOffset = position - dataSpec.position;
long initialPositionOffset = positionOffset;
long endOffset = length != C.LENGTH_UNSET ? positionOffset + length : C.POSITION_UNSET;
while (true) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
index 1b9b4a3..43bf691 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
@@ -45,11 +45,11 @@
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -494,7 +494,7 @@
private final boolean encrypt;
@Nullable private final Cipher cipher;
@Nullable private final SecretKeySpec secretKeySpec;
- @Nullable private final Random random;
+ @Nullable private final SecureRandom random;
private final AtomicFile atomicFile;
private boolean changed;
@@ -517,7 +517,7 @@
this.encrypt = encrypt;
this.cipher = cipher;
this.secretKeySpec = secretKeySpec;
- random = encrypt ? new Random() : null;
+ random = encrypt ? new SecureRandom() : null;
atomicFile = new AtomicFile(file);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java
index 15a827b..c3fa9e3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.upstream.cache;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.util.Log;
@@ -196,7 +195,7 @@
}
@Override
- public int compareTo(@NonNull Region another) {
+ public int compareTo(Region another) {
return Util.compareLong(startOffset, another.startOffset);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
index 5f420c3..721dac4 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.upstream.cache;
import android.os.ConditionVariable;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.C;
@@ -260,7 +259,7 @@
// Start cache initialization.
final ConditionVariable conditionVariable = new ConditionVariable();
- new Thread("SimpleCache.initialize()") {
+ new Thread("ExoPlayer:SimpleCacheInit") {
@Override
public void run() {
synchronized (SimpleCache.this) {
@@ -332,7 +331,6 @@
}
}
- @NonNull
@Override
public synchronized NavigableSet<CacheSpan> getCachedSpans(String key) {
Assertions.checkState(!released);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java
index ce195ba..d8a0671 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java
@@ -131,7 +131,7 @@
return null;
}
- int id = Integer.parseInt(matcher.group(1));
+ int id = Integer.parseInt(Assertions.checkNotNull(matcher.group(1)));
@Nullable String key = index.getKeyForId(id);
if (key == null) {
return null;
@@ -144,9 +144,9 @@
return null;
}
- long position = Long.parseLong(matcher.group(2));
+ long position = Long.parseLong(Assertions.checkNotNull(matcher.group(2)));
if (lastTouchTimestamp == C.TIME_UNSET) {
- lastTouchTimestamp = Long.parseLong(matcher.group(3));
+ lastTouchTimestamp = Long.parseLong(Assertions.checkNotNull(matcher.group(3)));
}
return new SimpleCacheSpan(key, position, length, lastTouchTimestamp, file);
}
@@ -165,11 +165,11 @@
String filename = file.getName();
Matcher matcher = CACHE_FILE_PATTERN_V2.matcher(filename);
if (matcher.matches()) {
- key = Util.unescapeFileName(matcher.group(1));
+ key = Util.unescapeFileName(Assertions.checkNotNull(matcher.group(1)));
} else {
matcher = CACHE_FILE_PATTERN_V1.matcher(filename);
if (matcher.matches()) {
- key = matcher.group(1); // Keys were not escaped in version 1.
+ key = Assertions.checkNotNull(matcher.group(1)); // Keys were not escaped in version 1.
}
}
@@ -181,8 +181,8 @@
getCacheFile(
Assertions.checkStateNotNull(file.getParentFile()),
index.assignIdForKey(key),
- Long.parseLong(matcher.group(2)),
- Long.parseLong(matcher.group(3)));
+ Long.parseLong(Assertions.checkNotNull(matcher.group(2))),
+ Long.parseLong(Assertions.checkNotNull(matcher.group(3))));
if (!file.renameTo(newCacheFile)) {
return null;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
index d9b3ff0..9529503 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java
@@ -68,8 +68,9 @@
public void open(DataSpec dataSpec) throws IOException {
wrappedDataSink.open(dataSpec);
long nonce = CryptoUtil.getFNV64Hash(dataSpec.key);
- cipher = new AesFlushingCipher(Cipher.ENCRYPT_MODE, secretKey, nonce,
- dataSpec.absoluteStreamPosition);
+ cipher =
+ new AesFlushingCipher(
+ Cipher.ENCRYPT_MODE, secretKey, nonce, dataSpec.uriPositionOffset + dataSpec.position);
}
@Override
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
index 0910c63..665a471 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java
@@ -52,8 +52,9 @@
public long open(DataSpec dataSpec) throws IOException {
long dataLength = upstream.open(dataSpec);
long nonce = CryptoUtil.getFNV64Hash(dataSpec.key);
- cipher = new AesFlushingCipher(Cipher.DECRYPT_MODE, secretKey, nonce,
- dataSpec.absoluteStreamPosition);
+ cipher =
+ new AesFlushingCipher(
+ Cipher.DECRYPT_MODE, secretKey, nonce, dataSpec.uriPositionOffset + dataSpec.position);
return dataLength;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java
index 54f52e0..85ef43f 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java
@@ -15,7 +15,9 @@
*/
package com.google.android.exoplayer2.util;
+import android.graphics.Color;
import android.text.TextUtils;
+import androidx.annotation.ColorInt;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@@ -49,6 +51,7 @@
* @param colorExpression The color expression.
* @return The parsed ARGB color.
*/
+ @ColorInt
public static int parseTtmlColor(String colorExpression) {
return parseColorInternal(colorExpression, false);
}
@@ -59,10 +62,12 @@
* @param colorExpression The color expression.
* @return The parsed ARGB color.
*/
+ @ColorInt
public static int parseCssColor(String colorExpression) {
return parseColorInternal(colorExpression, true);
}
+ @ColorInt
private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) {
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
colorExpression = colorExpression.replace(" ", "");
@@ -83,22 +88,21 @@
Matcher matcher = (alphaHasFloatFormat ? RGBA_PATTERN_FLOAT_ALPHA : RGBA_PATTERN_INT_ALPHA)
.matcher(colorExpression);
if (matcher.matches()) {
- return argb(
- alphaHasFloatFormat ? (int) (255 * Float.parseFloat(matcher.group(4)))
- : Integer.parseInt(matcher.group(4), 10),
- Integer.parseInt(matcher.group(1), 10),
- Integer.parseInt(matcher.group(2), 10),
- Integer.parseInt(matcher.group(3), 10)
- );
+ return Color.argb(
+ alphaHasFloatFormat
+ ? (int) (255 * Float.parseFloat(Assertions.checkNotNull(matcher.group(4))))
+ : Integer.parseInt(Assertions.checkNotNull(matcher.group(4)), 10),
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(1)), 10),
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(2)), 10),
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(3)), 10));
}
} else if (colorExpression.startsWith(RGB)) {
Matcher matcher = RGB_PATTERN.matcher(colorExpression);
if (matcher.matches()) {
- return rgb(
- Integer.parseInt(matcher.group(1), 10),
- Integer.parseInt(matcher.group(2), 10),
- Integer.parseInt(matcher.group(3), 10)
- );
+ return Color.rgb(
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(1)), 10),
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(2)), 10),
+ Integer.parseInt(Assertions.checkNotNull(matcher.group(3)), 10));
}
} else {
// we use our own color map
@@ -110,14 +114,6 @@
throw new IllegalArgumentException();
}
- private static int argb(int alpha, int red, int green, int blue) {
- return (alpha << 24) | (red << 16) | (green << 8) | blue;
- }
-
- private static int rgb(int red, int green, int blue) {
- return argb(0xFF, red, green, blue);
- }
-
static {
COLOR_MAP = new HashMap<>();
COLOR_MAP.put("aliceblue", 0xFFF0F8FF);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java
index e72e72c..60bd4f0 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.util;
-import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
@@ -26,12 +25,13 @@
import android.os.Handler;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Generates a {@link SurfaceTexture} using EGL/GLES functions. */
-@TargetApi(17)
+@RequiresApi(17)
public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableListener, Runnable {
/** Listener to be called when the texture image on {@link SurfaceTexture} has been updated. */
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
index 6d5820d..3136556 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.util;
import android.os.SystemClock;
+import android.text.TextUtils;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
@@ -91,14 +92,22 @@
// AnalyticsListener
@Override
- public void onLoadingChanged(EventTime eventTime, boolean isLoading) {
+ public void onIsLoadingChanged(EventTime eventTime, boolean isLoading) {
logd(eventTime, "loading", Boolean.toString(isLoading));
}
@Override
- public void onPlayerStateChanged(
- EventTime eventTime, boolean playWhenReady, @Player.State int state) {
- logd(eventTime, "state", playWhenReady + ", " + getStateString(state));
+ public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {
+ logd(eventTime, "state", getStateString(state));
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ logd(
+ eventTime,
+ "playWhenReady",
+ playWhenReady + ", " + getPlayWhenReadyChangeReasonString(reason));
}
@Override
@@ -141,9 +150,12 @@
logd(
eventTime,
"playbackParameters",
- Util.formatInvariant(
- "speed=%.2f, pitch=%.2f, skipSilence=%s",
- playbackParameters.speed, playbackParameters.pitch, playbackParameters.skipSilence));
+ Util.formatInvariant("speed=%.2f", playbackParameters.speed));
+ }
+
+ @Override
+ public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
+ logd(eventTime, "playbackSpeed", Float.toString(playbackSpeed));
}
@Override
@@ -198,14 +210,16 @@
logd(eventTime, "tracks", "[]");
return;
}
- logd("tracks [" + getEventTimeString(eventTime) + ", ");
+ logd("tracks [" + getEventTimeString(eventTime));
// Log tracks associated to renderers.
int rendererCount = mappedTrackInfo.getRendererCount();
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
TrackSelection trackSelection = trackSelections.get(rendererIndex);
- if (rendererTrackGroups.length > 0) {
- logd(" Renderer:" + rendererIndex + " [");
+ if (rendererTrackGroups.length == 0) {
+ logd(" " + mappedTrackInfo.getRendererName(rendererIndex) + " []");
+ } else {
+ logd(" " + mappedTrackInfo.getRendererName(rendererIndex) + " [");
for (int groupIndex = 0; groupIndex < rendererTrackGroups.length; groupIndex++) {
TrackGroup trackGroup = rendererTrackGroups.get(groupIndex);
String adaptiveSupport =
@@ -249,7 +263,7 @@
// Log tracks not associated with a renderer.
TrackGroupArray unassociatedTrackGroups = mappedTrackInfo.getUnmappedTrackGroups();
if (unassociatedTrackGroups.length > 0) {
- logd(" Renderer:None [");
+ logd(" Unmapped [");
for (int groupIndex = 0; groupIndex < unassociatedTrackGroups.length; groupIndex++) {
logd(" Group:" + groupIndex + " [");
TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex);
@@ -276,13 +290,8 @@
}
@Override
- public void onSeekProcessed(EventTime eventTime) {
- logd(eventTime, "seekProcessed");
- }
-
- @Override
public void onMetadata(EventTime eventTime, Metadata metadata) {
- logd("metadata [" + getEventTimeString(eventTime) + ", ");
+ logd("metadata [" + getEventTimeString(eventTime));
printMetadata(metadata, " ");
logd("]");
}
@@ -312,6 +321,11 @@
}
@Override
+ public void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {
+ logd(eventTime, "skipSilenceEnabled", Boolean.toString(skipSilenceEnabled));
+ }
+
+ @Override
public void onVolumeChanged(EventTime eventTime, float volume) {
logd(eventTime, "volume", Float.toString(volume));
}
@@ -469,27 +483,26 @@
}
/**
- * Logs an error message and exception.
+ * Logs an error message.
*
* @param msg The message to log.
- * @param tr The exception to log.
*/
- protected void loge(String msg, @Nullable Throwable tr) {
- Log.e(tag, msg, tr);
+ protected void loge(String msg) {
+ Log.e(tag, msg);
}
// Internal methods
private void logd(EventTime eventTime, String eventName) {
- logd(getEventString(eventTime, eventName));
+ logd(getEventString(eventTime, eventName, /* eventDescription= */ null, /* throwable= */ null));
}
private void logd(EventTime eventTime, String eventName, String eventDescription) {
- logd(getEventString(eventTime, eventName, eventDescription));
+ logd(getEventString(eventTime, eventName, eventDescription, /* throwable= */ null));
}
private void loge(EventTime eventTime, String eventName, @Nullable Throwable throwable) {
- loge(getEventString(eventTime, eventName), throwable);
+ loge(getEventString(eventTime, eventName, /* eventDescription= */ null, throwable));
}
private void loge(
@@ -497,7 +510,7 @@
String eventName,
String eventDescription,
@Nullable Throwable throwable) {
- loge(getEventString(eventTime, eventName, eventDescription), throwable);
+ loge(getEventString(eventTime, eventName, eventDescription, throwable));
}
private void printInternalError(EventTime eventTime, String type, Exception e) {
@@ -510,12 +523,21 @@
}
}
- private String getEventString(EventTime eventTime, String eventName) {
- return eventName + " [" + getEventTimeString(eventTime) + "]";
- }
-
- private String getEventString(EventTime eventTime, String eventName, String eventDescription) {
- return eventName + " [" + getEventTimeString(eventTime) + ", " + eventDescription + "]";
+ private String getEventString(
+ EventTime eventTime,
+ String eventName,
+ @Nullable String eventDescription,
+ @Nullable Throwable throwable) {
+ String eventString = eventName + " [" + getEventTimeString(eventTime);
+ if (eventDescription != null) {
+ eventString += ", " + eventDescription;
+ }
+ @Nullable String throwableString = Log.getThrowableString(throwable);
+ if (!TextUtils.isEmpty(throwableString)) {
+ eventString += "\n " + throwableString.replace("\n", "\n ") + '\n';
+ }
+ eventString += "]";
+ return eventString;
}
private String getEventTimeString(EventTime eventTime) {
@@ -637,4 +659,22 @@
return "?";
}
}
+
+ private static String getPlayWhenReadyChangeReasonString(
+ @Player.PlayWhenReadyChangeReason int reason) {
+ switch (reason) {
+ case Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY:
+ return "AUDIO_BECOMING_NOISY";
+ case Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS:
+ return "AUDIO_FOCUS_LOSS";
+ case Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE:
+ return "REMOTE";
+ case Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST:
+ return "USER_REQUEST";
+ case Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM:
+ return "END_OF_MEDIA_ITEM";
+ default:
+ return "?";
+ }
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java
index c7feff5..e90d133 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java
@@ -17,24 +17,233 @@
import static android.opengl.GLU.gluErrorString;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.opengl.EGL14;
+import android.opengl.EGLDisplay;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
+import javax.microedition.khronos.egl.EGL10;
-/** GL utility methods. */
+/** GL utilities. */
public final class GlUtil {
+
+ /**
+ * GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
+ */
+ public static final class Attribute {
+
+ /** The name of the attribute in the GLSL sources. */
+ public final String name;
+
+ private final int index;
+ private final int location;
+
+ @Nullable private Buffer buffer;
+ private int size;
+
+ /**
+ * Creates a new GL attribute.
+ *
+ * @param program The identifier of a compiled and linked GLSL shader program.
+ * @param index The index of the attribute. After this instance has been constructed, the name
+ * of the attribute is available via the {@link #name} field.
+ */
+ public Attribute(int program, int index) {
+ int[] len = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, len, 0);
+
+ int[] type = new int[1];
+ int[] size = new int[1];
+ byte[] nameBytes = new byte[len[0]];
+ int[] ignore = new int[1];
+
+ GLES20.glGetActiveAttrib(program, index, len[0], ignore, 0, size, 0, type, 0, nameBytes, 0);
+ name = new String(nameBytes, 0, strlen(nameBytes));
+ location = GLES20.glGetAttribLocation(program, name);
+ this.index = index;
+ }
+
+ /**
+ * Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
+ * elements) to this {@link Attribute}.
+ *
+ * @param buffer Buffer to bind to this attribute.
+ * @param size Number of elements per vertex.
+ */
+ public void setBuffer(float[] buffer, int size) {
+ this.buffer = createBuffer(buffer);
+ this.size = size;
+ }
+
+ /**
+ * Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
+ *
+ * <p>Should be called before each drawing call.
+ */
+ public void bind() {
+ Buffer buffer = Assertions.checkNotNull(this.buffer, "call setBuffer before bind");
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ GLES20.glVertexAttribPointer(
+ location,
+ size, // count
+ GLES20.GL_FLOAT, // type
+ false, // normalize
+ 0, // stride
+ buffer);
+ GLES20.glEnableVertexAttribArray(index);
+ checkGlError();
+ }
+ }
+
+ /**
+ * GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
+ */
+ public static final class Uniform {
+
+ /** The name of the uniform in the GLSL sources. */
+ public final String name;
+
+ private final int location;
+ private final int type;
+ private final float[] value;
+
+ private int texId;
+ private int unit;
+
+ /**
+ * Creates a new GL uniform.
+ *
+ * @param program The identifier of a compiled and linked GLSL shader program.
+ * @param index The index of the uniform. After this instance has been constructed, the name of
+ * the uniform is available via the {@link #name} field.
+ */
+ public Uniform(int program, int index) {
+ int[] len = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0);
+
+ int[] type = new int[1];
+ int[] size = new int[1];
+ byte[] name = new byte[len[0]];
+ int[] ignore = new int[1];
+
+ GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0);
+ this.name = new String(name, 0, strlen(name));
+ location = GLES20.glGetUniformLocation(program, this.name);
+ this.type = type[0];
+
+ value = new float[1];
+ }
+
+ /**
+ * Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
+ *
+ * @param texId The GL texture identifier from which to sample.
+ * @param unit The GL texture unit index.
+ */
+ public void setSamplerTexId(int texId, int unit) {
+ this.texId = texId;
+ this.unit = unit;
+ }
+
+ /** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
+ public void setFloat(float value) {
+ this.value[0] = value;
+ }
+
+ /**
+ * Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)} or
+ * {@link #setFloat(float)}.
+ *
+ * <p>Should be called before each drawing call.
+ */
+ public void bind() {
+ if (type == GLES20.GL_FLOAT) {
+ GLES20.glUniform1fv(location, 1, value, 0);
+ checkGlError();
+ return;
+ }
+
+ if (texId == 0) {
+ throw new IllegalStateException("call setSamplerTexId before bind");
+ }
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
+ if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) {
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
+ } else if (type == GLES20.GL_SAMPLER_2D) {
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
+ } else {
+ throw new IllegalStateException("unexpected uniform type: " + type);
+ }
+ GLES20.glUniform1i(location, unit);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ checkGlError();
+ }
+ }
+
private static final String TAG = "GlUtil";
+ private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
+ private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
+
/** Class only contains static methods. */
private GlUtil() {}
/**
+ * Returns whether creating a GL context with {@value EXTENSION_PROTECTED_CONTENT} is possible. If
+ * {@code true}, the device supports a protected output path for DRM content when using GL.
+ */
+ public static boolean isProtectedContentExtensionSupported(Context context) {
+ if (Util.SDK_INT < 24) {
+ return false;
+ }
+ if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
+ // Samsung devices running Nougat are known to be broken. See
+ // https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
+ // Moto Z XT1650 is also affected. See
+ // https://github.com/google/ExoPlayer/issues/3215.
+ return false;
+ }
+ if (Util.SDK_INT < 26
+ && !context
+ .getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ // Pre API level 26 devices were not well tested unless they supported VR mode.
+ return false;
+ }
+
+ EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ @Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
+ return eglExtensions != null && eglExtensions.contains(EXTENSION_PROTECTED_CONTENT);
+ }
+
+ /**
+ * Returns whether creating a GL context with {@value EXTENSION_SURFACELESS_CONTEXT} is possible.
+ */
+ public static boolean isSurfacelessContextExtensionSupported() {
+ if (Util.SDK_INT < 17) {
+ return false;
+ }
+ EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ @Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
+ return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT);
+ }
+
+ /**
* If there is an OpenGl error, logs the error and if {@link
* ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}.
*/
@@ -90,6 +299,34 @@
return program;
}
+ /** Returns the {@link Attribute}s in the specified {@code program}. */
+ public static Attribute[] getAttributes(int program) {
+ int[] attributeCount = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0);
+ if (attributeCount[0] != 2) {
+ throw new IllegalStateException("expected two attributes");
+ }
+
+ Attribute[] attributes = new Attribute[attributeCount[0]];
+ for (int i = 0; i < attributeCount[0]; i++) {
+ attributes[i] = new Attribute(program, i);
+ }
+ return attributes;
+ }
+
+ /** Returns the {@link Uniform}s in the specified {@code program}. */
+ public static Uniform[] getUniforms(int program) {
+ int[] uniformCount = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0);
+
+ Uniform[] uniforms = new Uniform[uniformCount[0]];
+ for (int i = 0; i < uniformCount[0]; i++) {
+ uniforms[i] = new Uniform(program, i);
+ }
+
+ return uniforms;
+ }
+
/**
* Allocates a FloatBuffer with the given data.
*
@@ -151,4 +388,14 @@
throw new RuntimeException(errorMsg);
}
}
+
+ /** Returns the length of the null-terminated string in {@code strVal}. */
+ private static int strlen(byte[] strVal) {
+ for (int i = 0; i < strVal.length; ++i) {
+ if (strVal[i] == '\0') {
+ return i;
+ }
+ }
+ return strVal.length;
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
index e9f08a3..44c3c5e 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
@@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.util;
-import com.google.android.exoplayer2.PlaybackParameters;
-
/**
* Tracks the progression of media time.
*/
@@ -28,16 +26,13 @@
long getPositionUs();
/**
- * Attempts to set the playback parameters. The media clock may override these parameters if they
- * are not supported.
+ * Attempts to set the playback speed. The media clock may override the speed if changing the
+ * speed is not supported.
*
- * @param playbackParameters The playback parameters to attempt to set.
+ * @param playbackSpeed The playback speed to attempt to set.
*/
- void setPlaybackParameters(PlaybackParameters playbackParameters);
+ void setPlaybackSpeed(float playbackSpeed);
- /**
- * Returns the active playback parameters.
- */
- PlaybackParameters getPlaybackParameters();
-
+ /** Returns the active playback speed. */
+ float getPlaybackSpeed();
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcher.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcher.java
new file mode 100644
index 0000000..c58221a
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcher.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import androidx.annotation.CheckResult;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+
+/**
+ * Event dispatcher which forwards events to a list of registered listeners.
+ *
+ * <p>Adds the correct {@code windowIndex} and {@code mediaPeriodId} values (and {@code
+ * mediaTimeOffsetMs} if needed).
+ *
+ * <p>Allows listeners of any type to be registered, calls to {@link #dispatch} then provide the
+ * type of listener to forward to, which is used to filter the registered listeners.
+ */
+// TODO: Make this final when MediaSourceEventListener.EventDispatcher is deleted.
+public class MediaSourceEventDispatcher {
+
+ /**
+ * Functional interface to send an event with {@code windowIndex} and {@code mediaPeriodId}
+ * attached.
+ */
+ public interface EventWithPeriodId<T> {
+
+ /** Sends the event to a listener. */
+ void sendTo(T listener, int windowIndex, @Nullable MediaPeriodId mediaPeriodId);
+ }
+
+ /** The timeline window index reported with the events. */
+ public final int windowIndex;
+ /** The {@link MediaPeriodId} reported with the events. */
+ @Nullable public final MediaPeriodId mediaPeriodId;
+
+ // TODO: Make these private when MediaSourceEventListener.EventDispatcher is deleted.
+ protected final CopyOnWriteMultiset<ListenerInfo> listenerInfos;
+ // TODO: Define exactly what this means, and check it's always set correctly.
+ protected final long mediaTimeOffsetMs;
+
+ /** Creates an event dispatcher. */
+ public MediaSourceEventDispatcher() {
+ this(
+ /* listenerInfos= */ new CopyOnWriteMultiset<>(),
+ /* windowIndex= */ 0,
+ /* mediaPeriodId= */ null,
+ /* mediaTimeOffsetMs= */ 0);
+ }
+
+ protected MediaSourceEventDispatcher(
+ CopyOnWriteMultiset<ListenerInfo> listenerInfos,
+ int windowIndex,
+ @Nullable MediaPeriodId mediaPeriodId,
+ long mediaTimeOffsetMs) {
+ this.listenerInfos = listenerInfos;
+ this.windowIndex = windowIndex;
+ this.mediaPeriodId = mediaPeriodId;
+ this.mediaTimeOffsetMs = mediaTimeOffsetMs;
+ }
+
+ /**
+ * Creates a view of the event dispatcher with pre-configured window index, media period id, and
+ * media time offset.
+ *
+ * @param windowIndex The timeline window index to be reported with the events.
+ * @param mediaPeriodId The {@link MediaPeriodId} to be reported with the events.
+ * @param mediaTimeOffsetMs The offset to be added to all media times, in milliseconds.
+ * @return A view of the event dispatcher with the pre-configured parameters.
+ */
+ @CheckResult
+ public MediaSourceEventDispatcher withParameters(
+ int windowIndex, @Nullable MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs) {
+ return new MediaSourceEventDispatcher(
+ listenerInfos, windowIndex, mediaPeriodId, mediaTimeOffsetMs);
+ }
+
+ /**
+ * Adds a listener to the event dispatcher.
+ *
+ * <p>Calls to {@link #dispatch(EventWithPeriodId, Class)} will propagate to {@code eventListener}
+ * if the {@code listenerClass} types are equal.
+ *
+ * <p>The same listener instance can be added multiple times with different {@code listenerClass}
+ * values (i.e. if the instance implements multiple listener interfaces).
+ *
+ * <p>Duplicate {@code {eventListener, listenerClass}} pairs are also permitted. In this case an
+ * event dispatched to {@code listenerClass} will only be passed to the {@code eventListener}
+ * once.
+ *
+ * <p><b>NOTE</b>: This doesn't interact well with hierarchies of listener interfaces. If a
+ * listener is registered with a super-class type then it will only receive events dispatched
+ * directly to that super-class type. Similarly, if a listener is registered with a sub-class type
+ * then it will only receive events dispatched directly to that sub-class.
+ *
+ * @param handler A handler on the which listener events will be posted.
+ * @param eventListener The listener to be added.
+ * @param listenerClass The type used to register the listener. Can be a superclass of {@code
+ * eventListener}.
+ */
+ public <T> void addEventListener(Handler handler, T eventListener, Class<T> listenerClass) {
+ Assertions.checkNotNull(handler);
+ Assertions.checkNotNull(eventListener);
+ listenerInfos.add(new ListenerInfo(handler, eventListener, listenerClass));
+ }
+
+ /**
+ * Removes a listener from the event dispatcher.
+ *
+ * <p>If there are duplicate registrations of {@code {eventListener, listenerClass}} this will
+ * only remove one (so events dispatched to {@code listenerClass} will still be passed to {@code
+ * eventListener}).
+ *
+ * @param eventListener The listener to be removed.
+ * @param listenerClass The listener type passed to {@link #addEventListener(Handler, Object,
+ * Class)}.
+ */
+ public <T> void removeEventListener(T eventListener, Class<T> listenerClass) {
+ for (ListenerInfo listenerInfo : listenerInfos) {
+ if (listenerInfo.listener == eventListener
+ && listenerInfo.listenerClass.equals(listenerClass)) {
+ listenerInfos.remove(listenerInfo);
+ return;
+ }
+ }
+ }
+
+ /** Dispatches {@code event} to all registered listeners of type {@code listenerClass}. */
+ @SuppressWarnings("unchecked") // The cast is gated with listenerClass.isInstance()
+ public <T> void dispatch(EventWithPeriodId<T> event, Class<T> listenerClass) {
+ for (ListenerInfo listenerInfo : listenerInfos.elementSet()) {
+ if (listenerInfo.listenerClass.equals(listenerClass)) {
+ postOrRun(
+ listenerInfo.handler,
+ () -> event.sendTo((T) listenerInfo.listener, windowIndex, mediaPeriodId));
+ }
+ }
+ }
+
+ private static void postOrRun(Handler handler, Runnable runnable) {
+ if (handler.getLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ handler.post(runnable);
+ }
+ }
+
+ public static long adjustMediaTime(long mediaTimeUs, long mediaTimeOffsetMs) {
+ long mediaTimeMs = C.usToMs(mediaTimeUs);
+ return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs;
+ }
+
+ /** Container class for a {@link Handler}, {@code listener} and {@code listenerClass}. */
+ protected static final class ListenerInfo {
+
+ public final Handler handler;
+ public final Object listener;
+ public final Class<?> listenerClass;
+
+ public ListenerInfo(Handler handler, Object listener, Class<?> listenerClass) {
+ this.handler = handler;
+ this.listener = listener;
+ this.listenerClass = listenerClass;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ListenerInfo)) {
+ return false;
+ }
+
+ ListenerInfo that = (ListenerInfo) o;
+
+ // We deliberately only consider listener and listenerClass (and not handler) in equals() and
+ // hashcode() because the handler used to process the callbacks is an implementation detail.
+ return listener.equals(that.listener) && listenerClass.equals(that.listenerClass);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 31 * listener.hashCode();
+ return result + 31 * listenerClass.hashCode();
+ }
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
index e5f9aa6..e1df77a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.util;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
/**
* A {@link MediaClock} whose position advances with real time based on the playback parameters when
@@ -29,7 +29,8 @@
private boolean started;
private long baseUs;
private long baseElapsedMs;
- private PlaybackParameters playbackParameters;
+ private float playbackSpeed;
+ private int scaledUsPerMs;
/**
* Creates a new standalone media clock using the given {@link Clock} implementation.
@@ -38,7 +39,8 @@
*/
public StandaloneMediaClock(Clock clock) {
this.clock = clock;
- this.playbackParameters = PlaybackParameters.DEFAULT;
+ playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED;
+ scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
}
/**
@@ -78,27 +80,33 @@
long positionUs = baseUs;
if (started) {
long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs;
- if (playbackParameters.speed == 1f) {
+ if (playbackSpeed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs);
} else {
- positionUs += playbackParameters.getMediaTimeUsForPlayoutTimeMs(elapsedSinceBaseMs);
+ // Add the media time in microseconds that will elapse in elapsedSinceBaseMs milliseconds of
+ // wallclock time
+ positionUs += elapsedSinceBaseMs * scaledUsPerMs;
}
}
return positionUs;
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ public void setPlaybackSpeed(float playbackSpeed) {
// Store the current position as the new base, in case the playback speed has changed.
if (started) {
resetPosition(getPositionUs());
}
- this.playbackParameters = playbackParameters;
+ this.playbackSpeed = playbackSpeed;
+ scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return playbackParameters;
+ public float getPlaybackSpeed() {
+ return playbackSpeed;
}
+ private static int getScaledUsPerMs(float playbackSpeed) {
+ return Math.round(playbackSpeed * 1000f);
+ }
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
new file mode 100644
index 0000000..72c4ac2
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.video;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.Surface;
+import androidx.annotation.CallSuper;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.BaseRenderer;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.PlayerMessage.Target;
+import com.google.android.exoplayer2.decoder.Decoder;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.decoder.DecoderException;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.drm.DrmSession;
+import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
+import com.google.android.exoplayer2.source.SampleStream;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.TimedValueQueue;
+import com.google.android.exoplayer2.util.TraceUtil;
+import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Decodes and renders video using a {@link Decoder}.
+ *
+ * <p>This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)}
+ * on the playback thread:
+ *
+ * <ul>
+ * <li>Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload
+ * should be the target {@link Surface}, or null.
+ * <li>Message with type {@link #MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER} to set the output
+ * buffer renderer. The message payload should be the target {@link
+ * VideoDecoderOutputBufferRenderer}, or null.
+ * <li>Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for
+ * metadata associated with frames being rendered. The message payload should be the {@link
+ * VideoFrameMetadataListener}, or null.
+ * </ul>
+ */
+public abstract class DecoderVideoRenderer extends BaseRenderer {
+
+ /** Decoder reinitialization states. */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REINITIALIZATION_STATE_NONE,
+ REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
+ REINITIALIZATION_STATE_WAIT_END_OF_STREAM
+ })
+ private @interface ReinitializationState {}
+ /** The decoder does not need to be re-initialized. */
+ private static final int REINITIALIZATION_STATE_NONE = 0;
+ /**
+ * The input format has changed in a way that requires the decoder to be re-initialized, but we
+ * haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
+ * ensure that it outputs any remaining buffers before we release it.
+ */
+ private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
+ /**
+ * The input format has changed in a way that requires the decoder to be re-initialized, and we've
+ * signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
+ * end of stream signal to indicate that it has output any remaining buffers before we release it.
+ */
+ private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
+
+ private final long allowedJoiningTimeMs;
+ private final int maxDroppedFramesToNotify;
+ private final EventDispatcher eventDispatcher;
+ private final TimedValueQueue<Format> formatQueue;
+ private final DecoderInputBuffer flagsOnlyBuffer;
+
+ private Format inputFormat;
+ private Format outputFormat;
+ private Decoder<
+ VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
+ decoder;
+ private VideoDecoderInputBuffer inputBuffer;
+ private VideoDecoderOutputBuffer outputBuffer;
+ @Nullable private Surface surface;
+ @Nullable private VideoDecoderOutputBufferRenderer outputBufferRenderer;
+ @Nullable private VideoFrameMetadataListener frameMetadataListener;
+ @C.VideoOutputMode private int outputMode;
+
+ @Nullable private DrmSession decoderDrmSession;
+ @Nullable private DrmSession sourceDrmSession;
+
+ @ReinitializationState private int decoderReinitializationState;
+ private boolean decoderReceivedBuffers;
+
+ private boolean renderedFirstFrameAfterReset;
+ private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
+ private boolean renderedFirstFrameAfterEnable;
+ private long initialPositionUs;
+ private long joiningDeadlineMs;
+ private boolean waitingForKeys;
+ private boolean waitingForFirstSampleInFormat;
+
+ private boolean inputStreamEnded;
+ private boolean outputStreamEnded;
+ private int reportedWidth;
+ private int reportedHeight;
+
+ private long droppedFrameAccumulationStartTimeMs;
+ private int droppedFrames;
+ private int consecutiveDroppedFrameCount;
+ private int buffersInCodecCount;
+ private long lastRenderTimeUs;
+ private long outputStreamOffsetUs;
+
+ /** Decoder event counters used for debugging purposes. */
+ protected DecoderCounters decoderCounters;
+
+ /**
+ * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
+ * can attempt to seamlessly join an ongoing playback.
+ * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
+ * null if delivery of events is not required.
+ * @param eventListener A listener of events. May be null if delivery of events is not required.
+ * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
+ * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
+ */
+ protected DecoderVideoRenderer(
+ long allowedJoiningTimeMs,
+ @Nullable Handler eventHandler,
+ @Nullable VideoRendererEventListener eventListener,
+ int maxDroppedFramesToNotify) {
+ super(C.TRACK_TYPE_VIDEO);
+ this.allowedJoiningTimeMs = allowedJoiningTimeMs;
+ this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
+ joiningDeadlineMs = C.TIME_UNSET;
+ clearReportedVideoSize();
+ formatQueue = new TimedValueQueue<>();
+ flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
+ eventDispatcher = new EventDispatcher(eventHandler, eventListener);
+ decoderReinitializationState = REINITIALIZATION_STATE_NONE;
+ outputMode = C.VIDEO_OUTPUT_MODE_NONE;
+ }
+
+ // BaseRenderer implementation.
+
+ @Override
+ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
+ if (outputStreamEnded) {
+ return;
+ }
+
+ if (inputFormat == null) {
+ // We don't have a format yet, so try and read one.
+ FormatHolder formatHolder = getFormatHolder();
+ flagsOnlyBuffer.clear();
+ @SampleStream.ReadDataResult int result = readSource(formatHolder, flagsOnlyBuffer, true);
+ if (result == C.RESULT_FORMAT_READ) {
+ onInputFormatChanged(formatHolder);
+ } else if (result == C.RESULT_BUFFER_READ) {
+ // End of stream read having not read a format.
+ Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
+ inputStreamEnded = true;
+ outputStreamEnded = true;
+ return;
+ } else {
+ // We still don't have a format and can't make progress without one.
+ return;
+ }
+ }
+
+ // If we don't have a decoder yet, we need to instantiate one.
+ maybeInitDecoder();
+
+ if (decoder != null) {
+ try {
+ // Rendering loop.
+ TraceUtil.beginSection("drainAndFeed");
+ while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
+ while (feedInputBuffer()) {}
+ TraceUtil.endSection();
+ } catch (DecoderException e) {
+ throw createRendererException(e, inputFormat);
+ }
+ decoderCounters.ensureUpdated();
+ }
+ }
+
+ @Override
+ public boolean isEnded() {
+ return outputStreamEnded;
+ }
+
+ @Override
+ public boolean isReady() {
+ if (waitingForKeys) {
+ return false;
+ }
+ if (inputFormat != null
+ && (isSourceReady() || outputBuffer != null)
+ && (renderedFirstFrameAfterReset || !hasOutput())) {
+ // Ready. If we were joining then we've now joined, so clear the joining deadline.
+ joiningDeadlineMs = C.TIME_UNSET;
+ return true;
+ } else if (joiningDeadlineMs == C.TIME_UNSET) {
+ // Not joining.
+ return false;
+ } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) {
+ // Joining and still within the joining deadline.
+ return true;
+ } else {
+ // The joining deadline has been exceeded. Give up and clear the deadline.
+ joiningDeadlineMs = C.TIME_UNSET;
+ return false;
+ }
+ }
+
+ // PlayerMessage.Target implementation.
+
+ @Override
+ public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
+ if (messageType == MSG_SET_SURFACE) {
+ setOutputSurface((Surface) message);
+ } else if (messageType == MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) {
+ setOutputBufferRenderer((VideoDecoderOutputBufferRenderer) message);
+ } else if (messageType == MSG_SET_VIDEO_FRAME_METADATA_LISTENER) {
+ frameMetadataListener = (VideoFrameMetadataListener) message;
+ } else {
+ super.handleMessage(messageType, message);
+ }
+ }
+
+ // Protected methods.
+
+ @Override
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ decoderCounters = new DecoderCounters();
+ eventDispatcher.enabled(decoderCounters);
+ mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
+ renderedFirstFrameAfterEnable = false;
+ }
+
+ @Override
+ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
+ inputStreamEnded = false;
+ outputStreamEnded = false;
+ clearRenderedFirstFrame();
+ initialPositionUs = C.TIME_UNSET;
+ consecutiveDroppedFrameCount = 0;
+ if (decoder != null) {
+ flushDecoder();
+ }
+ if (joining) {
+ setJoiningDeadlineMs();
+ } else {
+ joiningDeadlineMs = C.TIME_UNSET;
+ }
+ formatQueue.clear();
+ }
+
+ @Override
+ protected void onStarted() {
+ droppedFrames = 0;
+ droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
+ lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
+ }
+
+ @Override
+ protected void onStopped() {
+ joiningDeadlineMs = C.TIME_UNSET;
+ maybeNotifyDroppedFrames();
+ }
+
+ @Override
+ protected void onDisabled() {
+ inputFormat = null;
+ waitingForKeys = false;
+ clearReportedVideoSize();
+ clearRenderedFirstFrame();
+ try {
+ setSourceDrmSession(null);
+ releaseDecoder();
+ } finally {
+ eventDispatcher.disabled(decoderCounters);
+ }
+ }
+
+ @Override
+ protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
+ // TODO: This shouldn't just update the output stream offset as long as there are still buffers
+ // of the previous stream in the decoder. It should also make sure to render the first frame of
+ // the next stream if the playback position reached the new stream.
+ outputStreamOffsetUs = offsetUs;
+ super.onStreamChanged(formats, offsetUs);
+ }
+
+ /**
+ * Called when a decoder has been created and configured.
+ *
+ * <p>The default implementation is a no-op.
+ *
+ * @param name The name of the decoder that was initialized.
+ * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
+ * finished.
+ * @param initializationDurationMs The time taken to initialize the decoder, in milliseconds.
+ */
+ @CallSuper
+ protected void onDecoderInitialized(
+ String name, long initializedTimestampMs, long initializationDurationMs) {
+ eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
+ }
+
+ /**
+ * Flushes the decoder.
+ *
+ * @throws ExoPlaybackException If an error occurs reinitializing a decoder.
+ */
+ @CallSuper
+ protected void flushDecoder() throws ExoPlaybackException {
+ waitingForKeys = false;
+ buffersInCodecCount = 0;
+ if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
+ releaseDecoder();
+ maybeInitDecoder();
+ } else {
+ inputBuffer = null;
+ if (outputBuffer != null) {
+ outputBuffer.release();
+ outputBuffer = null;
+ }
+ decoder.flush();
+ decoderReceivedBuffers = false;
+ }
+ }
+
+ /** Releases the decoder. */
+ @CallSuper
+ protected void releaseDecoder() {
+ inputBuffer = null;
+ outputBuffer = null;
+ decoderReinitializationState = REINITIALIZATION_STATE_NONE;
+ decoderReceivedBuffers = false;
+ buffersInCodecCount = 0;
+ if (decoder != null) {
+ decoder.release();
+ decoder = null;
+ decoderCounters.decoderReleaseCount++;
+ }
+ setDecoderDrmSession(null);
+ }
+
+ /**
+ * Called when a new format is read from the upstream source.
+ *
+ * @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
+ * @throws ExoPlaybackException If an error occurs (re-)initializing the decoder.
+ */
+ @CallSuper
+ protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
+ waitingForFirstSampleInFormat = true;
+ Format newFormat = Assertions.checkNotNull(formatHolder.format);
+ setSourceDrmSession(formatHolder.drmSession);
+ inputFormat = newFormat;
+
+ if (sourceDrmSession != decoderDrmSession) {
+ if (decoderReceivedBuffers) {
+ // Signal end of stream and wait for any final output buffers before re-initialization.
+ decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
+ } else {
+ // There aren't any final output buffers, so release the decoder immediately.
+ releaseDecoder();
+ maybeInitDecoder();
+ }
+ }
+
+ eventDispatcher.inputFormatChanged(inputFormat);
+ }
+
+ /**
+ * Called immediately before an input buffer is queued into the decoder.
+ *
+ * <p>The default implementation is a no-op.
+ *
+ * @param buffer The buffer that will be queued.
+ */
+ protected void onQueueInputBuffer(VideoDecoderInputBuffer buffer) {
+ // Do nothing.
+ }
+
+ /**
+ * Called when an output buffer is successfully processed.
+ *
+ * @param presentationTimeUs The timestamp associated with the output buffer.
+ */
+ @CallSuper
+ protected void onProcessedOutputBuffer(long presentationTimeUs) {
+ buffersInCodecCount--;
+ }
+
+ /**
+ * Returns whether the buffer being processed should be dropped.
+ *
+ * @param earlyUs The time until the buffer should be presented in microseconds. A negative value
+ * indicates that the buffer is late.
+ * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
+ * measured at the start of the current iteration of the rendering loop.
+ */
+ protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
+ return isBufferLate(earlyUs);
+ }
+
+ /**
+ * Returns whether to drop all buffers from the buffer being processed to the keyframe at or after
+ * the current playback position, if possible.
+ *
+ * @param earlyUs The time until the current buffer should be presented in microseconds. A
+ * negative value indicates that the buffer is late.
+ * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
+ * measured at the start of the current iteration of the rendering loop.
+ */
+ protected boolean shouldDropBuffersToKeyframe(long earlyUs, long elapsedRealtimeUs) {
+ return isBufferVeryLate(earlyUs);
+ }
+
+ /**
+ * Returns whether to force rendering an output buffer.
+ *
+ * @param earlyUs The time until the current buffer should be presented in microseconds. A
+ * negative value indicates that the buffer is late.
+ * @param elapsedSinceLastRenderUs The elapsed time since the last output buffer was rendered, in
+ * microseconds.
+ * @return Returns whether to force rendering an output buffer.
+ */
+ protected boolean shouldForceRenderOutputBuffer(long earlyUs, long elapsedSinceLastRenderUs) {
+ return isBufferLate(earlyUs) && elapsedSinceLastRenderUs > 100000;
+ }
+
+ /**
+ * Skips the specified output buffer and releases it.
+ *
+ * @param outputBuffer The output buffer to skip.
+ */
+ protected void skipOutputBuffer(VideoDecoderOutputBuffer outputBuffer) {
+ decoderCounters.skippedOutputBufferCount++;
+ outputBuffer.release();
+ }
+
+ /**
+ * Drops the specified output buffer and releases it.
+ *
+ * @param outputBuffer The output buffer to drop.
+ */
+ protected void dropOutputBuffer(VideoDecoderOutputBuffer outputBuffer) {
+ updateDroppedBufferCounters(1);
+ outputBuffer.release();
+ }
+
+ /**
+ * Drops frames from the current output buffer to the next keyframe at or before the playback
+ * position. If no such keyframe exists, as the playback position is inside the same group of
+ * pictures as the buffer being processed, returns {@code false}. Returns {@code true} otherwise.
+ *
+ * @param positionUs The current playback position, in microseconds.
+ * @return Whether any buffers were dropped.
+ * @throws ExoPlaybackException If an error occurs flushing the decoder.
+ */
+ protected boolean maybeDropBuffersToKeyframe(long positionUs) throws ExoPlaybackException {
+ int droppedSourceBufferCount = skipSource(positionUs);
+ if (droppedSourceBufferCount == 0) {
+ return false;
+ }
+ decoderCounters.droppedToKeyframeCount++;
+ // We dropped some buffers to catch up, so update the decoder counters and flush the decoder,
+ // which releases all pending buffers buffers including the current output buffer.
+ updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
+ flushDecoder();
+ return true;
+ }
+
+ /**
+ * Updates decoder counters to reflect that {@code droppedBufferCount} additional buffers were
+ * dropped.
+ *
+ * @param droppedBufferCount The number of additional dropped buffers.
+ */
+ protected void updateDroppedBufferCounters(int droppedBufferCount) {
+ decoderCounters.droppedBufferCount += droppedBufferCount;
+ droppedFrames += droppedBufferCount;
+ consecutiveDroppedFrameCount += droppedBufferCount;
+ decoderCounters.maxConsecutiveDroppedBufferCount =
+ Math.max(consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedBufferCount);
+ if (maxDroppedFramesToNotify > 0 && droppedFrames >= maxDroppedFramesToNotify) {
+ maybeNotifyDroppedFrames();
+ }
+ }
+
+ /**
+ * Creates a decoder for the given format.
+ *
+ * @param format The format for which a decoder is required.
+ * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
+ * May be null and can be ignored if decoder does not handle encrypted content.
+ * @return The decoder.
+ * @throws DecoderException If an error occurred creating a suitable decoder.
+ */
+ protected abstract Decoder<
+ VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
+ createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) throws DecoderException;
+
+ /**
+ * Renders the specified output buffer.
+ *
+ * <p>The implementation of this method takes ownership of the output buffer and is responsible
+ * for calling {@link VideoDecoderOutputBuffer#release()} either immediately or in the future.
+ *
+ * @param outputBuffer {@link VideoDecoderOutputBuffer} to render.
+ * @param presentationTimeUs Presentation time in microseconds.
+ * @param outputFormat Output {@link Format}.
+ * @throws DecoderException If an error occurs when rendering the output buffer.
+ */
+ protected void renderOutputBuffer(
+ VideoDecoderOutputBuffer outputBuffer, long presentationTimeUs, Format outputFormat)
+ throws DecoderException {
+ if (frameMetadataListener != null) {
+ frameMetadataListener.onVideoFrameAboutToBeRendered(
+ presentationTimeUs, System.nanoTime(), outputFormat, /* mediaFormat= */ null);
+ }
+ lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000);
+ int bufferMode = outputBuffer.mode;
+ boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && surface != null;
+ boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null;
+ if (!renderYuv && !renderSurface) {
+ dropOutputBuffer(outputBuffer);
+ } else {
+ maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height);
+ if (renderYuv) {
+ outputBufferRenderer.setOutputBuffer(outputBuffer);
+ } else {
+ renderOutputBufferToSurface(outputBuffer, surface);
+ }
+ consecutiveDroppedFrameCount = 0;
+ decoderCounters.renderedOutputBufferCount++;
+ maybeNotifyRenderedFirstFrame();
+ }
+ }
+
+ /**
+ * Renders the specified output buffer to the passed surface.
+ *
+ * <p>The implementation of this method takes ownership of the output buffer and is responsible
+ * for calling {@link VideoDecoderOutputBuffer#release()} either immediately or in the future.
+ *
+ * @param outputBuffer {@link VideoDecoderOutputBuffer} to render.
+ * @param surface Output {@link Surface}.
+ * @throws DecoderException If an error occurs when rendering the output buffer.
+ */
+ protected abstract void renderOutputBufferToSurface(
+ VideoDecoderOutputBuffer outputBuffer, Surface surface) throws DecoderException;
+
+ /**
+ * Sets output surface.
+ *
+ * @param surface Surface.
+ */
+ protected final void setOutputSurface(@Nullable Surface surface) {
+ if (this.surface != surface) {
+ // The output has changed.
+ this.surface = surface;
+ if (surface != null) {
+ outputBufferRenderer = null;
+ outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV;
+ if (decoder != null) {
+ setDecoderOutputMode(outputMode);
+ }
+ onOutputChanged();
+ } else {
+ // The output has been removed. We leave the outputMode of the underlying decoder unchanged
+ // in anticipation that a subsequent output will likely be of the same type.
+ outputMode = C.VIDEO_OUTPUT_MODE_NONE;
+ onOutputRemoved();
+ }
+ } else if (surface != null) {
+ // The output is unchanged and non-null.
+ onOutputReset();
+ }
+ }
+
+ /**
+ * Sets output buffer renderer.
+ *
+ * @param outputBufferRenderer Output buffer renderer.
+ */
+ protected final void setOutputBufferRenderer(
+ @Nullable VideoDecoderOutputBufferRenderer outputBufferRenderer) {
+ if (this.outputBufferRenderer != outputBufferRenderer) {
+ // The output has changed.
+ this.outputBufferRenderer = outputBufferRenderer;
+ if (outputBufferRenderer != null) {
+ surface = null;
+ outputMode = C.VIDEO_OUTPUT_MODE_YUV;
+ if (decoder != null) {
+ setDecoderOutputMode(outputMode);
+ }
+ onOutputChanged();
+ } else {
+ // The output has been removed. We leave the outputMode of the underlying decoder unchanged
+ // in anticipation that a subsequent output will likely be of the same type.
+ outputMode = C.VIDEO_OUTPUT_MODE_NONE;
+ onOutputRemoved();
+ }
+ } else if (outputBufferRenderer != null) {
+ // The output is unchanged and non-null.
+ onOutputReset();
+ }
+ }
+
+ /**
+ * Sets output mode of the decoder.
+ *
+ * @param outputMode Output mode.
+ */
+ protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
+
+ // Internal methods.
+
+ private void setSourceDrmSession(@Nullable DrmSession session) {
+ DrmSession.replaceSession(sourceDrmSession, session);
+ sourceDrmSession = session;
+ }
+
+ private void setDecoderDrmSession(@Nullable DrmSession session) {
+ DrmSession.replaceSession(decoderDrmSession, session);
+ decoderDrmSession = session;
+ }
+
+ private void maybeInitDecoder() throws ExoPlaybackException {
+ if (decoder != null) {
+ return;
+ }
+
+ setDecoderDrmSession(sourceDrmSession);
+
+ ExoMediaCrypto mediaCrypto = null;
+ if (decoderDrmSession != null) {
+ mediaCrypto = decoderDrmSession.getMediaCrypto();
+ if (mediaCrypto == null) {
+ DrmSessionException drmError = decoderDrmSession.getError();
+ if (drmError != null) {
+ // Continue for now. We may be able to avoid failure if the session recovers, or if a new
+ // input format causes the session to be replaced before it's used.
+ } else {
+ // The drm session isn't open yet.
+ return;
+ }
+ }
+ }
+
+ try {
+ long decoderInitializingTimestamp = SystemClock.elapsedRealtime();
+ decoder = createDecoder(inputFormat, mediaCrypto);
+ setDecoderOutputMode(outputMode);
+ long decoderInitializedTimestamp = SystemClock.elapsedRealtime();
+ onDecoderInitialized(
+ decoder.getName(),
+ decoderInitializedTimestamp,
+ decoderInitializedTimestamp - decoderInitializingTimestamp);
+ decoderCounters.decoderInitCount++;
+ } catch (DecoderException e) {
+ throw createRendererException(e, inputFormat);
+ }
+ }
+
+ private boolean feedInputBuffer() throws DecoderException, ExoPlaybackException {
+ if (decoder == null
+ || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
+ || inputStreamEnded) {
+ // We need to reinitialize the decoder or the input stream has ended.
+ return false;
+ }
+
+ if (inputBuffer == null) {
+ inputBuffer = decoder.dequeueInputBuffer();
+ if (inputBuffer == null) {
+ return false;
+ }
+ }
+
+ if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
+ inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
+ decoder.queueInputBuffer(inputBuffer);
+ inputBuffer = null;
+ decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
+ return false;
+ }
+
+ @SampleStream.ReadDataResult int result;
+ FormatHolder formatHolder = getFormatHolder();
+ if (waitingForKeys) {
+ // We've already read an encrypted sample into buffer, and are waiting for keys.
+ result = C.RESULT_BUFFER_READ;
+ } else {
+ result = readSource(formatHolder, inputBuffer, false);
+ }
+
+ if (result == C.RESULT_NOTHING_READ) {
+ return false;
+ }
+ if (result == C.RESULT_FORMAT_READ) {
+ onInputFormatChanged(formatHolder);
+ return true;
+ }
+ if (inputBuffer.isEndOfStream()) {
+ inputStreamEnded = true;
+ decoder.queueInputBuffer(inputBuffer);
+ inputBuffer = null;
+ return false;
+ }
+ boolean bufferEncrypted = inputBuffer.isEncrypted();
+ waitingForKeys = shouldWaitForKeys(bufferEncrypted);
+ if (waitingForKeys) {
+ return false;
+ }
+ if (waitingForFirstSampleInFormat) {
+ formatQueue.add(inputBuffer.timeUs, inputFormat);
+ waitingForFirstSampleInFormat = false;
+ }
+ inputBuffer.flip();
+ inputBuffer.colorInfo = inputFormat.colorInfo;
+ onQueueInputBuffer(inputBuffer);
+ decoder.queueInputBuffer(inputBuffer);
+ buffersInCodecCount++;
+ decoderReceivedBuffers = true;
+ decoderCounters.inputBufferCount++;
+ inputBuffer = null;
+ return true;
+ }
+
+ /**
+ * Attempts to dequeue an output buffer from the decoder and, if successful, passes it to {@link
+ * #processOutputBuffer(long, long)}.
+ *
+ * @param positionUs The player's current position.
+ * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
+ * measured at the start of the current iteration of the rendering loop.
+ * @return Whether it may be possible to drain more output data.
+ * @throws ExoPlaybackException If an error occurs draining the output buffer.
+ */
+ private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
+ throws ExoPlaybackException, DecoderException {
+ if (outputBuffer == null) {
+ outputBuffer = decoder.dequeueOutputBuffer();
+ if (outputBuffer == null) {
+ return false;
+ }
+ decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
+ buffersInCodecCount -= outputBuffer.skippedOutputBufferCount;
+ }
+
+ if (outputBuffer.isEndOfStream()) {
+ if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
+ // We're waiting to re-initialize the decoder, and have now processed all final buffers.
+ releaseDecoder();
+ maybeInitDecoder();
+ } else {
+ outputBuffer.release();
+ outputBuffer = null;
+ outputStreamEnded = true;
+ }
+ return false;
+ }
+
+ boolean processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs);
+ if (processedOutputBuffer) {
+ onProcessedOutputBuffer(outputBuffer.timeUs);
+ outputBuffer = null;
+ }
+ return processedOutputBuffer;
+ }
+
+ /**
+ * Processes {@link #outputBuffer} by rendering it, skipping it or doing nothing, and returns
+ * whether it may be possible to process another output buffer.
+ *
+ * @param positionUs The player's current position.
+ * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
+ * measured at the start of the current iteration of the rendering loop.
+ * @return Whether it may be possible to drain another output buffer.
+ * @throws ExoPlaybackException If an error occurs processing the output buffer.
+ */
+ private boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs)
+ throws ExoPlaybackException, DecoderException {
+ if (initialPositionUs == C.TIME_UNSET) {
+ initialPositionUs = positionUs;
+ }
+
+ long earlyUs = outputBuffer.timeUs - positionUs;
+ if (!hasOutput()) {
+ // Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
+ if (isBufferLate(earlyUs)) {
+ skipOutputBuffer(outputBuffer);
+ return true;
+ }
+ return false;
+ }
+
+ long presentationTimeUs = outputBuffer.timeUs - outputStreamOffsetUs;
+ Format format = formatQueue.pollFloor(presentationTimeUs);
+ if (format != null) {
+ outputFormat = format;
+ }
+
+ long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
+ long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs;
+ boolean isStarted = getState() == STATE_STARTED;
+ boolean shouldRenderFirstFrame =
+ !renderedFirstFrameAfterEnable
+ ? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
+ : !renderedFirstFrameAfterReset;
+ // TODO: We shouldn't force render while we are joining an ongoing playback.
+ if (shouldRenderFirstFrame
+ || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))) {
+ renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
+ return true;
+ }
+
+ if (!isStarted || positionUs == initialPositionUs) {
+ return false;
+ }
+
+ // TODO: Treat dropped buffers as skipped while we are joining an ongoing playback.
+ if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs)
+ && maybeDropBuffersToKeyframe(positionUs)) {
+ return false;
+ } else if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
+ dropOutputBuffer(outputBuffer);
+ return true;
+ }
+
+ if (earlyUs < 30000) {
+ renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean hasOutput() {
+ return outputMode != C.VIDEO_OUTPUT_MODE_NONE;
+ }
+
+ private void onOutputChanged() {
+ // If we know the video size, report it again immediately.
+ maybeRenotifyVideoSizeChanged();
+ // We haven't rendered to the new output yet.
+ clearRenderedFirstFrame();
+ if (getState() == STATE_STARTED) {
+ setJoiningDeadlineMs();
+ }
+ }
+
+ private void onOutputRemoved() {
+ clearReportedVideoSize();
+ clearRenderedFirstFrame();
+ }
+
+ private void onOutputReset() {
+ // The output is unchanged and non-null. If we know the video size and/or have already
+ // rendered to the output, report these again immediately.
+ maybeRenotifyVideoSizeChanged();
+ maybeRenotifyRenderedFirstFrame();
+ }
+
+ private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
+ DrmSession decoderDrmSession = this.decoderDrmSession;
+ if (decoderDrmSession == null
+ || (!bufferEncrypted && decoderDrmSession.playClearSamplesWithoutKeys())) {
+ return false;
+ }
+ @DrmSession.State int drmSessionState = decoderDrmSession.getState();
+ if (drmSessionState == DrmSession.STATE_ERROR) {
+ throw createRendererException(decoderDrmSession.getError(), inputFormat);
+ }
+ return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
+ }
+
+ private void setJoiningDeadlineMs() {
+ joiningDeadlineMs =
+ allowedJoiningTimeMs > 0
+ ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs)
+ : C.TIME_UNSET;
+ }
+
+ private void clearRenderedFirstFrame() {
+ renderedFirstFrameAfterReset = false;
+ }
+
+ private void maybeNotifyRenderedFirstFrame() {
+ renderedFirstFrameAfterEnable = true;
+ if (!renderedFirstFrameAfterReset) {
+ renderedFirstFrameAfterReset = true;
+ eventDispatcher.renderedFirstFrame(surface);
+ }
+ }
+
+ private void maybeRenotifyRenderedFirstFrame() {
+ if (renderedFirstFrameAfterReset) {
+ eventDispatcher.renderedFirstFrame(surface);
+ }
+ }
+
+ private void clearReportedVideoSize() {
+ reportedWidth = Format.NO_VALUE;
+ reportedHeight = Format.NO_VALUE;
+ }
+
+ private void maybeNotifyVideoSizeChanged(int width, int height) {
+ if (reportedWidth != width || reportedHeight != height) {
+ reportedWidth = width;
+ reportedHeight = height;
+ eventDispatcher.videoSizeChanged(
+ width, height, /* unappliedRotationDegrees= */ 0, /* pixelWidthHeightRatio= */ 1);
+ }
+ }
+
+ private void maybeRenotifyVideoSizeChanged() {
+ if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
+ eventDispatcher.videoSizeChanged(
+ reportedWidth,
+ reportedHeight,
+ /* unappliedRotationDegrees= */ 0,
+ /* pixelWidthHeightRatio= */ 1);
+ }
+ }
+
+ private void maybeNotifyDroppedFrames() {
+ if (droppedFrames > 0) {
+ long now = SystemClock.elapsedRealtime();
+ long elapsedMs = now - droppedFrameAccumulationStartTimeMs;
+ eventDispatcher.droppedFrames(droppedFrames, elapsedMs);
+ droppedFrames = 0;
+ droppedFrameAccumulationStartTimeMs = now;
+ }
+ }
+
+ private static boolean isBufferLate(long earlyUs) {
+ // Class a buffer as late if it should have been presented more than 30 ms ago.
+ return earlyUs < -30000;
+ }
+
+ private static boolean isBufferVeryLate(long earlyUs) {
+ // Class a buffer as very late if it should have been presented more than 500 ms ago.
+ return earlyUs < -500000;
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
index d1f874b..a5dd9cf 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/DummySurface.java
@@ -19,37 +19,29 @@
import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_PROTECTED_PBUFFER;
import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_SURFACELESS_CONTEXT;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.EGLDisplay;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
import android.view.Surface;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.EGLSurfaceTexture;
import com.google.android.exoplayer2.util.EGLSurfaceTexture.SecureMode;
+import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
-import javax.microedition.khronos.egl.EGL10;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-/**
- * A dummy {@link Surface}.
- */
-@TargetApi(17)
+/** A dummy {@link Surface}. */
+@RequiresApi(17)
public final class DummySurface extends Surface {
private static final String TAG = "DummySurface";
- private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
- private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
-
/**
* Whether the surface is secure.
*/
@@ -69,7 +61,7 @@
*/
public static synchronized boolean isSecureSupported(Context context) {
if (!secureModeInitialized) {
- secureMode = Util.SDK_INT < 24 ? SECURE_MODE_NONE : getSecureModeV24(context);
+ secureMode = getSecureMode(context);
secureModeInitialized = true;
}
return secureMode != SECURE_MODE_NONE;
@@ -88,7 +80,6 @@
* {@link #isSecureSupported(Context)} returns {@code false}.
*/
public static DummySurface newInstanceV17(Context context, boolean secure) {
- assertApiLevel17OrHigher();
Assertions.checkState(!secure || isSecureSupported(context));
DummySurfaceThread thread = new DummySurfaceThread();
return thread.init(secure ? secureMode : SECURE_MODE_NONE);
@@ -115,40 +106,21 @@
}
}
- private static void assertApiLevel17OrHigher() {
- if (Util.SDK_INT < 17) {
- throw new UnsupportedOperationException("Unsupported prior to API level 17");
- }
- }
-
- @TargetApi(24)
- private static @SecureMode int getSecureModeV24(Context context) {
- if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
- // Samsung devices running Nougat are known to be broken. See
- // https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
- // Moto Z XT1650 is also affected. See
- // https://github.com/google/ExoPlayer/issues/3215.
+ @SecureMode
+ private static int getSecureMode(Context context) {
+ if (GlUtil.isProtectedContentExtensionSupported(context)) {
+ if (GlUtil.isSurfacelessContextExtensionSupported()) {
+ return SECURE_MODE_SURFACELESS_CONTEXT;
+ } else {
+ // If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface.
+ // This may require support for EXT_protected_surface, but in practice it works on some
+ // devices that don't have that extension. See also
+ // https://github.com/google/ExoPlayer/issues/3558.
+ return SECURE_MODE_PROTECTED_PBUFFER;
+ }
+ } else {
return SECURE_MODE_NONE;
}
- if (Util.SDK_INT < 26 && !context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
- // Pre API level 26 devices were not well tested unless they supported VR mode.
- return SECURE_MODE_NONE;
- }
- EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
- if (eglExtensions == null) {
- return SECURE_MODE_NONE;
- }
- if (!eglExtensions.contains(EXTENSION_PROTECTED_CONTENT)) {
- return SECURE_MODE_NONE;
- }
- // If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface. This may
- // require support for EXT_protected_surface, but in practice it works on some devices that
- // don't have that extension. See also https://github.com/google/ExoPlayer/issues/3558.
- return eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT)
- ? SECURE_MODE_SURFACELESS_CONTEXT
- : SECURE_MODE_PROTECTED_PBUFFER;
}
private static class DummySurfaceThread extends HandlerThread implements Callback {
@@ -163,7 +135,7 @@
@Nullable private DummySurface surface;
public DummySurfaceThread() {
- super("dummySurface");
+ super("ExoPlayer:DummySurface");
}
public DummySurface init(@SecureMode int secureMode) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoDecoderException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoDecoderException.java
new file mode 100644
index 0000000..9846ecd
--- /dev/null
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoDecoderException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.video;
+
+import android.media.MediaCodec;
+import android.view.Surface;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
+import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
+
+/** Thrown when a failure occurs in a {@link MediaCodec} video decoder. */
+public class MediaCodecVideoDecoderException extends MediaCodecDecoderException {
+
+ /** The {@link System#identityHashCode(Object)} of the surface when the exception occurred. */
+ public final int surfaceIdentityHashCode;
+
+ /** Whether the surface was valid when the exception occurred. */
+ public final boolean isSurfaceValid;
+
+ public MediaCodecVideoDecoderException(
+ Throwable cause, @Nullable MediaCodecInfo codecInfo, @Nullable Surface surface) {
+ super(cause, codecInfo);
+ surfaceIdentityHashCode = System.identityHashCode(surface);
+ isSurfaceValid = surface == null || surface.isValid();
+ }
+}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
index 2031554..794bc5f 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
@@ -32,6 +32,7 @@
import android.view.Surface;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
@@ -41,15 +42,13 @@
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmInitData;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
+import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
-import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
@@ -73,6 +72,9 @@
* payload should be one of the integer scaling modes in {@link VideoScalingMode}. Note that
* the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by
* a {@link android.view.SurfaceView}.
+ * <li>Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for
+ * metadata associated with frames being rendered. The message payload should be the {@link
+ * VideoFrameMetadataListener}, or null.
* </ul>
*/
public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@@ -87,9 +89,6 @@
private static final int[] STANDARD_LONG_EDGE_VIDEO_PX = new int[] {
1920, 1600, 1440, 1280, 960, 854, 640, 540, 480};
- // Generally there is zero or one pending output stream offset. We track more offsets to allow for
- // pending output streams that have fewer frames than the codec latency.
- private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10;
/**
* Scale factor for the initial maximum input size used to configure the codec in non-adaptive
* playbacks. See {@link #getCodecMaxValues(MediaCodecInfo, Format, Format[])}.
@@ -99,23 +98,6 @@
/** Magic frame render timestamp that indicates the EOS in tunneling mode. */
private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
- /** A {@link DecoderException} with additional surface information. */
- public static final class VideoDecoderException extends DecoderException {
-
- /** The {@link System#identityHashCode(Object)} of the surface when the exception occurred. */
- public final int surfaceIdentityHashCode;
-
- /** Whether the surface was valid when the exception occurred. */
- public final boolean isSurfaceValid;
-
- public VideoDecoderException(
- Throwable cause, @Nullable MediaCodecInfo codecInfo, @Nullable Surface surface) {
- super(cause, codecInfo);
- surfaceIdentityHashCode = System.identityHashCode(surface);
- isSurfaceValid = surface == null || surface.isValid();
- }
- }
-
private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround;
private static boolean deviceNeedsSetOutputSurfaceWorkaround;
@@ -125,8 +107,6 @@
private final long allowedJoiningTimeMs;
private final int maxDroppedFramesToNotify;
private final boolean deviceNeedsNoPostProcessWorkaround;
- private final long[] pendingOutputStreamOffsetsUs;
- private final long[] pendingOutputStreamSwitchTimesUs;
private CodecMaxValues codecMaxValues;
private boolean codecNeedsSetOutputSurfaceWorkaround;
@@ -135,7 +115,9 @@
private Surface surface;
private Surface dummySurface;
@VideoScalingMode private int scalingMode;
- private boolean renderedFirstFrame;
+ private boolean renderedFirstFrameAfterReset;
+ private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
+ private boolean renderedFirstFrameAfterEnable;
private long initialPositionUs;
private long joiningDeadlineMs;
private long droppedFrameAccumulationStartTimeMs;
@@ -143,6 +125,8 @@
private int consecutiveDroppedFrameCount;
private int buffersInCodecCount;
private long lastRenderTimeUs;
+ private long totalVideoFrameProcessingOffsetUs;
+ private int videoFrameProcessingOffsetCount;
private int pendingRotationDegrees;
private float pendingPixelWidthHeightRatio;
@@ -159,10 +143,6 @@
private boolean tunneling;
private int tunnelingAudioSessionId;
/* package */ @Nullable OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener;
-
- private long lastInputTimeUs;
- private long outputStreamOffsetUs;
- private int pendingOutputStreamOffsetCount;
@Nullable private VideoFrameMetadataListener frameMetadataListener;
/**
@@ -201,7 +181,6 @@
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
*/
- @SuppressWarnings("deprecation")
public MediaCodecVideoRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@@ -213,51 +192,6 @@
context,
mediaCodecSelector,
allowedJoiningTimeMs,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify);
- }
-
- /**
- * @param context A context.
- * @param mediaCodecSelector A decoder selector.
- * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
- * can attempt to seamlessly join an ongoing playback.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
- * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
- * @deprecated Use {@link #MediaCodecVideoRenderer(Context, MediaCodecSelector, long, boolean,
- * Handler, VideoRendererEventListener, int)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
- */
- @Deprecated
- @SuppressWarnings("deprecation")
- public MediaCodecVideoRenderer(
- Context context,
- MediaCodecSelector mediaCodecSelector,
- long allowedJoiningTimeMs,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- @Nullable Handler eventHandler,
- @Nullable VideoRendererEventListener eventListener,
- int maxDroppedFramesToNotify) {
- this(
- context,
- mediaCodecSelector,
- allowedJoiningTimeMs,
- drmSessionManager,
- playClearSamplesWithoutKeys,
/* enableDecoderFallback= */ false,
eventHandler,
eventListener,
@@ -278,7 +212,6 @@
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
*/
- @SuppressWarnings("deprecation")
public MediaCodecVideoRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@@ -287,58 +220,9 @@
@Nullable Handler eventHandler,
@Nullable VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) {
- this(
- context,
- mediaCodecSelector,
- allowedJoiningTimeMs,
- /* drmSessionManager= */ null,
- /* playClearSamplesWithoutKeys= */ false,
- enableDecoderFallback,
- eventHandler,
- eventListener,
- maxDroppedFramesToNotify);
- }
-
- /**
- * @param context A context.
- * @param mediaCodecSelector A decoder selector.
- * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
- * can attempt to seamlessly join an ongoing playback.
- * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
- * content is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
- * initialization fails. This may result in using a decoder that is slower/less efficient than
- * the primary decoder.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
- * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
- * @deprecated Use {@link #MediaCodecVideoRenderer(Context, MediaCodecSelector, long, boolean,
- * Handler, VideoRendererEventListener, int)} instead, and pass DRM-related parameters to the
- * {@link MediaSource} factories.
- */
- @Deprecated
- public MediaCodecVideoRenderer(
- Context context,
- MediaCodecSelector mediaCodecSelector,
- long allowedJoiningTimeMs,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
- boolean enableDecoderFallback,
- @Nullable Handler eventHandler,
- @Nullable VideoRendererEventListener eventListener,
- int maxDroppedFramesToNotify) {
super(
C.TRACK_TYPE_VIDEO,
mediaCodecSelector,
- drmSessionManager,
- playClearSamplesWithoutKeys,
enableDecoderFallback,
/* assumedMinimumCodecOperatingRate= */ 30);
this.allowedJoiningTimeMs = allowedJoiningTimeMs;
@@ -347,10 +231,6 @@
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround();
- pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
- pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
- outputStreamOffsetUs = C.TIME_UNSET;
- lastInputTimeUs = C.TIME_UNSET;
joiningDeadlineMs = C.TIME_UNSET;
currentWidth = Format.NO_VALUE;
currentHeight = Format.NO_VALUE;
@@ -361,11 +241,13 @@
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
- protected int supportsFormat(
- MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- Format format)
+ protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
String mimeType = format.sampleMimeType;
if (!MimeTypes.isVideo(mimeType)) {
@@ -392,12 +274,7 @@
if (decoderInfos.isEmpty()) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
}
- boolean supportsFormatDrm =
- drmInitData == null
- || FrameworkMediaCrypto.class.equals(format.exoMediaCryptoType)
- || (format.exoMediaCryptoType == null
- && supportsFormatDrm(drmSessionManager, drmInitData));
- if (!supportsFormatDrm) {
+ if (!supportsFormatDrm(format)) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM);
}
// Check capabilities for the first decoder in the list, which takes priority.
@@ -472,8 +349,9 @@
}
@Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- super.onEnabled(joining);
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ super.onEnabled(joining, mayRenderStartOfStream);
int oldTunnelingAudioSessionId = tunnelingAudioSessionId;
tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET;
@@ -482,23 +360,8 @@
}
eventDispatcher.enabled(decoderCounters);
frameReleaseTimeHelper.enable();
- }
-
- @Override
- protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
- if (outputStreamOffsetUs == C.TIME_UNSET) {
- outputStreamOffsetUs = offsetUs;
- } else {
- if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) {
- Log.w(TAG, "Too many stream changes, so dropping offset: "
- + pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
- } else {
- pendingOutputStreamOffsetCount++;
- }
- pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs;
- pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] = lastInputTimeUs;
- }
- super.onStreamChanged(formats, offsetUs);
+ mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
+ renderedFirstFrameAfterEnable = false;
}
@Override
@@ -507,11 +370,6 @@
clearRenderedFirstFrame();
initialPositionUs = C.TIME_UNSET;
consecutiveDroppedFrameCount = 0;
- lastInputTimeUs = C.TIME_UNSET;
- if (pendingOutputStreamOffsetCount != 0) {
- outputStreamOffsetUs = pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1];
- pendingOutputStreamOffsetCount = 0;
- }
if (joining) {
setJoiningDeadlineMs();
} else {
@@ -521,8 +379,11 @@
@Override
public boolean isReady() {
- if (super.isReady() && (renderedFirstFrame || (dummySurface != null && surface == dummySurface)
- || getCodec() == null || tunneling)) {
+ if (super.isReady()
+ && (renderedFirstFrameAfterReset
+ || (dummySurface != null && surface == dummySurface)
+ || getCodec() == null
+ || tunneling)) {
// Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineMs = C.TIME_UNSET;
return true;
@@ -545,20 +406,20 @@
droppedFrames = 0;
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
+ totalVideoFrameProcessingOffsetUs = 0;
+ videoFrameProcessingOffsetCount = 0;
}
@Override
protected void onStopped() {
joiningDeadlineMs = C.TIME_UNSET;
maybeNotifyDroppedFrames();
+ maybeNotifyVideoFrameProcessingOffset();
super.onStopped();
}
@Override
protected void onDisabled() {
- lastInputTimeUs = C.TIME_UNSET;
- outputStreamOffsetUs = C.TIME_UNSET;
- pendingOutputStreamOffsetCount = 0;
currentMediaFormat = null;
clearReportedVideoSize();
clearRenderedFirstFrame();
@@ -679,7 +540,9 @@
deviceNeedsNoPostProcessWorkaround,
tunnelingAudioSessionId);
if (surface == null) {
- Assertions.checkState(shouldUseDummySurface(codecInfo));
+ if (!shouldUseDummySurface(codecInfo)) {
+ throw new IllegalStateException();
+ }
if (dummySurface == null) {
dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure);
}
@@ -759,7 +622,6 @@
if (!tunneling) {
buffersInCodecCount++;
}
- lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs);
if (Util.SDK_INT < 23 && tunneling) {
// In tunneled mode before API 23 we don't have a way to know when the buffer is output, so
// treat it as if it were output immediately.
@@ -788,9 +650,11 @@
+ 1
: outputMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
processOutputFormat(codec, mediaFormatWidth, mediaFormatHeight);
+ maybeNotifyVideoFrameProcessingOffset();
}
@Override
+ @TargetApi(29) // codecHandlesHdr10PlusOutOfBandMetadata is false if Util.SDK_INT < 29
protected void handleInputBufferSupplementalData(DecoderInputBuffer buffer)
throws ExoPlaybackException {
if (!codecHandlesHdr10PlusOutOfBandMetadata) {
@@ -815,7 +679,6 @@
byte[] hdr10PlusInfo = new byte[data.remaining()];
data.get(hdr10PlusInfo);
data.position(0);
- // If codecHandlesHdr10PlusOutOfBandMetadata is true, this is an API 29 or later build.
setHdr10PlusInfoV29(getCodec(), hdr10PlusInfo);
}
}
@@ -829,6 +692,7 @@
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
+ int sampleCount,
long bufferPresentationTimeUs,
boolean isDecodeOnlyBuffer,
boolean isLastBuffer,
@@ -838,6 +702,7 @@
initialPositionUs = positionUs;
}
+ long outputStreamOffsetUs = getOutputStreamOffsetUs();
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
if (isDecodeOnlyBuffer && !isLastBuffer) {
@@ -850,6 +715,7 @@
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
if (isBufferLate(earlyUs)) {
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
+ decoderCounters.addVideoFrameProcessingOffsetSample(earlyUs);
return true;
}
return false;
@@ -858,11 +724,15 @@
long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs;
boolean isStarted = getState() == STATE_STARTED;
+ boolean shouldRenderFirstFrame =
+ !renderedFirstFrameAfterEnable
+ ? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
+ : !renderedFirstFrameAfterReset;
// Don't force output until we joined and the position reached the current stream.
boolean forceRenderOutputBuffer =
joiningDeadlineMs == C.TIME_UNSET
&& positionUs >= outputStreamOffsetUs
- && (!renderedFirstFrame
+ && (shouldRenderFirstFrame
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
if (forceRenderOutputBuffer) {
long releaseTimeNs = System.nanoTime();
@@ -872,6 +742,7 @@
} else {
renderOutputBuffer(codec, bufferIndex, presentationTimeUs);
}
+ decoderCounters.addVideoFrameProcessingOffsetSample(earlyUs);
return true;
}
@@ -904,6 +775,7 @@
} else {
dropOutputBuffer(codec, bufferIndex, presentationTimeUs);
}
+ decoderCounters.addVideoFrameProcessingOffsetSample(earlyUs);
return true;
}
@@ -913,6 +785,7 @@
notifyFrameMetadataListener(
presentationTimeUs, adjustedReleaseTimeNs, format, currentMediaFormat);
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs);
+ decoderCounters.addVideoFrameProcessingOffsetSample(earlyUs);
return true;
}
} else {
@@ -932,6 +805,7 @@
notifyFrameMetadataListener(
presentationTimeUs, adjustedReleaseTimeNs, format, currentMediaFormat);
renderOutputBuffer(codec, bufferIndex, presentationTimeUs);
+ decoderCounters.addVideoFrameProcessingOffsetSample(earlyUs);
return true;
}
}
@@ -970,15 +844,6 @@
}
}
- /**
- * Returns the offset that should be subtracted from {@code bufferPresentationTimeUs} in {@link
- * #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, long, boolean, boolean,
- * Format)} to get the playback position with respect to the media.
- */
- protected long getOutputStreamOffsetUs() {
- return outputStreamOffsetUs;
- }
-
/** Called when a buffer was processed in tunneling mode. */
protected void onProcessedTunneledBuffer(long presentationTimeUs) {
@Nullable Format format = updateOutputFormatForTime(presentationTimeUs);
@@ -986,6 +851,7 @@
processOutputFormat(getCodec(), format.width, format.height);
}
maybeNotifyVideoSizeChanged();
+ decoderCounters.renderedOutputBufferCount++;
maybeNotifyRenderedFirstFrame();
onProcessedOutputBuffer(presentationTimeUs);
}
@@ -995,35 +861,19 @@
setPendingOutputEndOfStream();
}
- /**
- * Called when an output buffer is successfully processed.
- *
- * @param presentationTimeUs The timestamp associated with the output buffer.
- */
@CallSuper
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
+ super.onProcessedOutputBuffer(presentationTimeUs);
if (!tunneling) {
buffersInCodecCount--;
}
- while (pendingOutputStreamOffsetCount != 0
- && presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) {
- outputStreamOffsetUs = pendingOutputStreamOffsetsUs[0];
- pendingOutputStreamOffsetCount--;
- System.arraycopy(
- pendingOutputStreamOffsetsUs,
- /* srcPos= */ 1,
- pendingOutputStreamOffsetsUs,
- /* destPos= */ 0,
- pendingOutputStreamOffsetCount);
- System.arraycopy(
- pendingOutputStreamSwitchTimesUs,
- /* srcPos= */ 1,
- pendingOutputStreamSwitchTimesUs,
- /* destPos= */ 0,
- pendingOutputStreamOffsetCount);
- clearRenderedFirstFrame();
- }
+ }
+
+ @Override
+ protected void onProcessedStreamChange() {
+ super.onProcessedStreamChange();
+ clearRenderedFirstFrame();
}
/**
@@ -1180,7 +1030,7 @@
* @param presentationTimeUs The presentation time of the output buffer, in microseconds.
* @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds.
*/
- @TargetApi(21)
+ @RequiresApi(21)
protected void renderOutputBufferV21(
MediaCodec codec, int index, long presentationTimeUs, long releaseTimeNs) {
maybeNotifyVideoSizeChanged();
@@ -1206,7 +1056,7 @@
}
private void clearRenderedFirstFrame() {
- renderedFirstFrame = false;
+ renderedFirstFrameAfterReset = false;
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
// non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and
// OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and
@@ -1221,14 +1071,15 @@
}
/* package */ void maybeNotifyRenderedFirstFrame() {
- if (!renderedFirstFrame) {
- renderedFirstFrame = true;
+ renderedFirstFrameAfterEnable = true;
+ if (!renderedFirstFrameAfterReset) {
+ renderedFirstFrameAfterReset = true;
eventDispatcher.renderedFirstFrame(surface);
}
}
private void maybeRenotifyRenderedFirstFrame() {
- if (renderedFirstFrame) {
+ if (renderedFirstFrameAfterReset) {
eventDispatcher.renderedFirstFrame(surface);
}
}
@@ -1271,6 +1122,22 @@
}
}
+ private void maybeNotifyVideoFrameProcessingOffset() {
+ Format outputFormat = getCurrentOutputFormat();
+ if (outputFormat != null) {
+ long totalOffsetDelta =
+ decoderCounters.totalVideoFrameProcessingOffsetUs - totalVideoFrameProcessingOffsetUs;
+ int countDelta =
+ decoderCounters.videoFrameProcessingOffsetCount - videoFrameProcessingOffsetCount;
+ if (countDelta != 0) {
+ eventDispatcher.reportVideoFrameProcessingOffset(
+ totalOffsetDelta, countDelta, outputFormat);
+ totalVideoFrameProcessingOffsetUs = decoderCounters.totalVideoFrameProcessingOffsetUs;
+ videoFrameProcessingOffsetCount = decoderCounters.videoFrameProcessingOffsetCount;
+ }
+ }
+ }
+
private static boolean isBufferLate(long earlyUs) {
// Class a buffer as late if it should have been presented more than 30 ms ago.
return earlyUs < -30000;
@@ -1281,19 +1148,19 @@
return earlyUs < -500000;
}
- @TargetApi(29)
+ @RequiresApi(29)
private static void setHdr10PlusInfoV29(MediaCodec codec, byte[] hdr10PlusInfo) {
Bundle codecParameters = new Bundle();
codecParameters.putByteArray(MediaCodec.PARAMETER_KEY_HDR10_PLUS_INFO, hdr10PlusInfo);
codec.setParameters(codecParameters);
}
- @TargetApi(23)
+ @RequiresApi(23)
private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) {
codec.setOutputSurface(surface);
}
- @TargetApi(21)
+ @RequiresApi(21)
private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) {
mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
@@ -1314,6 +1181,7 @@
* @return The framework {@link MediaFormat} that should be used to configure the decoder.
*/
@SuppressLint("InlinedApi")
+ @TargetApi(21) // tunnelingAudioSessionId is unset if Util.SDK_INT < 21
protected MediaFormat getMediaFormat(
Format format,
String codecMimeType,
@@ -1422,9 +1290,9 @@
}
@Override
- protected DecoderException createDecoderException(
+ protected MediaCodecDecoderException createDecoderException(
Throwable cause, @Nullable MediaCodecInfo codecInfo) {
- return new VideoDecoderException(cause, codecInfo, surface);
+ return new MediaCodecVideoDecoderException(cause, codecInfo, surface);
}
/**
@@ -1601,9 +1469,14 @@
}
synchronized (MediaCodecVideoRenderer.class) {
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
- if (Util.SDK_INT <= 27 && ("dangal".equals(Util.DEVICE) || "HWEML".equals(Util.DEVICE))) {
- // A small number of devices are affected on API level 27:
- // https://github.com/google/ExoPlayer/issues/5169.
+ if ("dangal".equals(Util.DEVICE)) {
+ // Workaround for MiTV devices:
+ // https://github.com/google/ExoPlayer/issues/5169,
+ // https://github.com/google/ExoPlayer/issues/6899.
+ deviceNeedsSetOutputSurfaceWorkaround = true;
+ } else if (Util.SDK_INT <= 27 && "HWEML".equals(Util.DEVICE)) {
+ // Workaround for Huawei P20:
+ // https://github.com/google/ExoPlayer/issues/4468#issuecomment-459291645.
deviceNeedsSetOutputSurfaceWorkaround = true;
} else if (Util.SDK_INT >= 27) {
// In general, devices running API level 27 or later should be unaffected. Do nothing.
@@ -1792,7 +1665,7 @@
}
- @TargetApi(23)
+ @RequiresApi(23)
private final class OnFrameRenderedListenerV23
implements MediaCodec.OnFrameRenderedListener, Handler.Callback {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/SimpleDecoderVideoRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/SimpleDecoderVideoRenderer.java
deleted file mode 100644
index bf0a28f..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/SimpleDecoderVideoRenderer.java
+++ /dev/null
@@ -1,962 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.video;
-
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.Surface;
-import androidx.annotation.CallSuper;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.BaseRenderer;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.RendererCapabilities;
-import com.google.android.exoplayer2.decoder.DecoderCounters;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.drm.DrmSession;
-import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.TimedValueQueue;
-import com.google.android.exoplayer2.util.TraceUtil;
-import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Decodes and renders video using a {@link SimpleDecoder}. */
-public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
-
- /** Decoder reinitialization states. */
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- REINITIALIZATION_STATE_NONE,
- REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
- REINITIALIZATION_STATE_WAIT_END_OF_STREAM
- })
- private @interface ReinitializationState {}
- /** The decoder does not need to be re-initialized. */
- private static final int REINITIALIZATION_STATE_NONE = 0;
- /**
- * The input format has changed in a way that requires the decoder to be re-initialized, but we
- * haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
- * ensure that it outputs any remaining buffers before we release it.
- */
- private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
- /**
- * The input format has changed in a way that requires the decoder to be re-initialized, and we've
- * signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
- * end of stream signal to indicate that it has output any remaining buffers before we release it.
- */
- private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
-
- private final long allowedJoiningTimeMs;
- private final int maxDroppedFramesToNotify;
- private final boolean playClearSamplesWithoutKeys;
- private final EventDispatcher eventDispatcher;
- private final TimedValueQueue<Format> formatQueue;
- private final DecoderInputBuffer flagsOnlyBuffer;
- private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
-
- private Format inputFormat;
- private Format outputFormat;
- private SimpleDecoder<
- VideoDecoderInputBuffer,
- ? extends VideoDecoderOutputBuffer,
- ? extends VideoDecoderException>
- decoder;
- private VideoDecoderInputBuffer inputBuffer;
- private VideoDecoderOutputBuffer outputBuffer;
- @Nullable private Surface surface;
- @Nullable private VideoDecoderOutputBufferRenderer outputBufferRenderer;
- @C.VideoOutputMode private int outputMode;
-
- @Nullable private DrmSession<ExoMediaCrypto> decoderDrmSession;
- @Nullable private DrmSession<ExoMediaCrypto> sourceDrmSession;
-
- @ReinitializationState private int decoderReinitializationState;
- private boolean decoderReceivedBuffers;
-
- private boolean renderedFirstFrame;
- private long initialPositionUs;
- private long joiningDeadlineMs;
- private boolean waitingForKeys;
- private boolean waitingForFirstSampleInFormat;
-
- private boolean inputStreamEnded;
- private boolean outputStreamEnded;
- private int reportedWidth;
- private int reportedHeight;
-
- private long droppedFrameAccumulationStartTimeMs;
- private int droppedFrames;
- private int consecutiveDroppedFrameCount;
- private int buffersInCodecCount;
- private long lastRenderTimeUs;
- private long outputStreamOffsetUs;
-
- /** Decoder event counters used for debugging purposes. */
- protected DecoderCounters decoderCounters;
-
- /**
- * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
- * can attempt to seamlessly join an ongoing playback.
- * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
- * null if delivery of events is not required.
- * @param eventListener A listener of events. May be null if delivery of events is not required.
- * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
- * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
- * @param drmSessionManager For use with encrypted media. May be null if support for encrypted
- * media is not required.
- * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
- * For example a media file may start with a short clear region so as to allow playback to
- * begin in parallel with key acquisition. This parameter specifies whether the renderer is
- * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
- * has obtained the keys necessary to decrypt encrypted regions of the media.
- */
- protected SimpleDecoderVideoRenderer(
- long allowedJoiningTimeMs,
- @Nullable Handler eventHandler,
- @Nullable VideoRendererEventListener eventListener,
- int maxDroppedFramesToNotify,
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys) {
- super(C.TRACK_TYPE_VIDEO);
- this.allowedJoiningTimeMs = allowedJoiningTimeMs;
- this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
- this.drmSessionManager = drmSessionManager;
- this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
- joiningDeadlineMs = C.TIME_UNSET;
- clearReportedVideoSize();
- formatQueue = new TimedValueQueue<>();
- flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
- eventDispatcher = new EventDispatcher(eventHandler, eventListener);
- decoderReinitializationState = REINITIALIZATION_STATE_NONE;
- outputMode = C.VIDEO_OUTPUT_MODE_NONE;
- }
-
- // BaseRenderer implementation.
-
- @Override
- @Capabilities
- public final int supportsFormat(Format format) {
- return supportsFormatInternal(drmSessionManager, format);
- }
-
- @Override
- public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- if (outputStreamEnded) {
- return;
- }
-
- if (inputFormat == null) {
- // We don't have a format yet, so try and read one.
- FormatHolder formatHolder = getFormatHolder();
- flagsOnlyBuffer.clear();
- int result = readSource(formatHolder, flagsOnlyBuffer, true);
- if (result == C.RESULT_FORMAT_READ) {
- onInputFormatChanged(formatHolder);
- } else if (result == C.RESULT_BUFFER_READ) {
- // End of stream read having not read a format.
- Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
- inputStreamEnded = true;
- outputStreamEnded = true;
- return;
- } else {
- // We still don't have a format and can't make progress without one.
- return;
- }
- }
-
- // If we don't have a decoder yet, we need to instantiate one.
- maybeInitDecoder();
-
- if (decoder != null) {
- try {
- // Rendering loop.
- TraceUtil.beginSection("drainAndFeed");
- while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
- while (feedInputBuffer()) {}
- TraceUtil.endSection();
- } catch (VideoDecoderException e) {
- throw createRendererException(e, inputFormat);
- }
- decoderCounters.ensureUpdated();
- }
- }
-
- @Override
- public boolean isEnded() {
- return outputStreamEnded;
- }
-
- @Override
- public boolean isReady() {
- if (waitingForKeys) {
- return false;
- }
- if (inputFormat != null
- && (isSourceReady() || outputBuffer != null)
- && (renderedFirstFrame || !hasOutput())) {
- // Ready. If we were joining then we've now joined, so clear the joining deadline.
- joiningDeadlineMs = C.TIME_UNSET;
- return true;
- } else if (joiningDeadlineMs == C.TIME_UNSET) {
- // Not joining.
- return false;
- } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) {
- // Joining and still within the joining deadline.
- return true;
- } else {
- // The joining deadline has been exceeded. Give up and clear the deadline.
- joiningDeadlineMs = C.TIME_UNSET;
- return false;
- }
- }
-
- // Protected methods.
-
- @Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- decoderCounters = new DecoderCounters();
- eventDispatcher.enabled(decoderCounters);
- }
-
- @Override
- protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
- inputStreamEnded = false;
- outputStreamEnded = false;
- clearRenderedFirstFrame();
- initialPositionUs = C.TIME_UNSET;
- consecutiveDroppedFrameCount = 0;
- if (decoder != null) {
- flushDecoder();
- }
- if (joining) {
- setJoiningDeadlineMs();
- } else {
- joiningDeadlineMs = C.TIME_UNSET;
- }
- formatQueue.clear();
- }
-
- @Override
- protected void onStarted() {
- droppedFrames = 0;
- droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
- lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
- }
-
- @Override
- protected void onStopped() {
- joiningDeadlineMs = C.TIME_UNSET;
- maybeNotifyDroppedFrames();
- }
-
- @Override
- protected void onDisabled() {
- inputFormat = null;
- waitingForKeys = false;
- clearReportedVideoSize();
- clearRenderedFirstFrame();
- try {
- setSourceDrmSession(null);
- releaseDecoder();
- } finally {
- eventDispatcher.disabled(decoderCounters);
- }
- }
-
- @Override
- protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
- outputStreamOffsetUs = offsetUs;
- super.onStreamChanged(formats, offsetUs);
- }
-
- /**
- * Called when a decoder has been created and configured.
- *
- * <p>The default implementation is a no-op.
- *
- * @param name The name of the decoder that was initialized.
- * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
- * finished.
- * @param initializationDurationMs The time taken to initialize the decoder, in milliseconds.
- */
- @CallSuper
- protected void onDecoderInitialized(
- String name, long initializedTimestampMs, long initializationDurationMs) {
- eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
- }
-
- /**
- * Flushes the decoder.
- *
- * @throws ExoPlaybackException If an error occurs reinitializing a decoder.
- */
- @CallSuper
- protected void flushDecoder() throws ExoPlaybackException {
- waitingForKeys = false;
- buffersInCodecCount = 0;
- if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
- releaseDecoder();
- maybeInitDecoder();
- } else {
- inputBuffer = null;
- if (outputBuffer != null) {
- outputBuffer.release();
- outputBuffer = null;
- }
- decoder.flush();
- decoderReceivedBuffers = false;
- }
- }
-
- /** Releases the decoder. */
- @CallSuper
- protected void releaseDecoder() {
- inputBuffer = null;
- outputBuffer = null;
- decoderReinitializationState = REINITIALIZATION_STATE_NONE;
- decoderReceivedBuffers = false;
- buffersInCodecCount = 0;
- if (decoder != null) {
- decoder.release();
- decoder = null;
- decoderCounters.decoderReleaseCount++;
- }
- setDecoderDrmSession(null);
- }
-
- /**
- * Called when a new format is read from the upstream source.
- *
- * @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
- * @throws ExoPlaybackException If an error occurs (re-)initializing the decoder.
- */
- @CallSuper
- @SuppressWarnings("unchecked")
- protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
- waitingForFirstSampleInFormat = true;
- Format newFormat = Assertions.checkNotNull(formatHolder.format);
- if (formatHolder.includesDrmSession) {
- setSourceDrmSession((DrmSession<ExoMediaCrypto>) formatHolder.drmSession);
- } else {
- sourceDrmSession =
- getUpdatedSourceDrmSession(inputFormat, newFormat, drmSessionManager, sourceDrmSession);
- }
- inputFormat = newFormat;
-
- if (sourceDrmSession != decoderDrmSession) {
- if (decoderReceivedBuffers) {
- // Signal end of stream and wait for any final output buffers before re-initialization.
- decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
- } else {
- // There aren't any final output buffers, so release the decoder immediately.
- releaseDecoder();
- maybeInitDecoder();
- }
- }
-
- eventDispatcher.inputFormatChanged(inputFormat);
- }
-
- /**
- * Called immediately before an input buffer is queued into the decoder.
- *
- * <p>The default implementation is a no-op.
- *
- * @param buffer The buffer that will be queued.
- */
- protected void onQueueInputBuffer(VideoDecoderInputBuffer buffer) {
- // Do nothing.
- }
-
- /**
- * Called when an output buffer is successfully processed.
- *
- * @param presentationTimeUs The timestamp associated with the output buffer.
- */
- @CallSuper
- protected void onProcessedOutputBuffer(long presentationTimeUs) {
- buffersInCodecCount--;
- }
-
- /**
- * Returns whether the buffer being processed should be dropped.
- *
- * @param earlyUs The time until the buffer should be presented in microseconds. A negative value
- * indicates that the buffer is late.
- * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
- * measured at the start of the current iteration of the rendering loop.
- */
- protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
- return isBufferLate(earlyUs);
- }
-
- /**
- * Returns whether to drop all buffers from the buffer being processed to the keyframe at or after
- * the current playback position, if possible.
- *
- * @param earlyUs The time until the current buffer should be presented in microseconds. A
- * negative value indicates that the buffer is late.
- * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
- * measured at the start of the current iteration of the rendering loop.
- */
- protected boolean shouldDropBuffersToKeyframe(long earlyUs, long elapsedRealtimeUs) {
- return isBufferVeryLate(earlyUs);
- }
-
- /**
- * Returns whether to force rendering an output buffer.
- *
- * @param earlyUs The time until the current buffer should be presented in microseconds. A
- * negative value indicates that the buffer is late.
- * @param elapsedSinceLastRenderUs The elapsed time since the last output buffer was rendered, in
- * microseconds.
- * @return Returns whether to force rendering an output buffer.
- */
- protected boolean shouldForceRenderOutputBuffer(long earlyUs, long elapsedSinceLastRenderUs) {
- return isBufferLate(earlyUs) && elapsedSinceLastRenderUs > 100000;
- }
-
- /**
- * Skips the specified output buffer and releases it.
- *
- * @param outputBuffer The output buffer to skip.
- */
- protected void skipOutputBuffer(VideoDecoderOutputBuffer outputBuffer) {
- decoderCounters.skippedOutputBufferCount++;
- outputBuffer.release();
- }
-
- /**
- * Drops the specified output buffer and releases it.
- *
- * @param outputBuffer The output buffer to drop.
- */
- protected void dropOutputBuffer(VideoDecoderOutputBuffer outputBuffer) {
- updateDroppedBufferCounters(1);
- outputBuffer.release();
- }
-
- /**
- * Drops frames from the current output buffer to the next keyframe at or before the playback
- * position. If no such keyframe exists, as the playback position is inside the same group of
- * pictures as the buffer being processed, returns {@code false}. Returns {@code true} otherwise.
- *
- * @param positionUs The current playback position, in microseconds.
- * @return Whether any buffers were dropped.
- * @throws ExoPlaybackException If an error occurs flushing the decoder.
- */
- protected boolean maybeDropBuffersToKeyframe(long positionUs) throws ExoPlaybackException {
- int droppedSourceBufferCount = skipSource(positionUs);
- if (droppedSourceBufferCount == 0) {
- return false;
- }
- decoderCounters.droppedToKeyframeCount++;
- // We dropped some buffers to catch up, so update the decoder counters and flush the decoder,
- // which releases all pending buffers buffers including the current output buffer.
- updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
- flushDecoder();
- return true;
- }
-
- /**
- * Updates decoder counters to reflect that {@code droppedBufferCount} additional buffers were
- * dropped.
- *
- * @param droppedBufferCount The number of additional dropped buffers.
- */
- protected void updateDroppedBufferCounters(int droppedBufferCount) {
- decoderCounters.droppedBufferCount += droppedBufferCount;
- droppedFrames += droppedBufferCount;
- consecutiveDroppedFrameCount += droppedBufferCount;
- decoderCounters.maxConsecutiveDroppedBufferCount =
- Math.max(consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedBufferCount);
- if (maxDroppedFramesToNotify > 0 && droppedFrames >= maxDroppedFramesToNotify) {
- maybeNotifyDroppedFrames();
- }
- }
-
- /**
- * Returns the {@link Capabilities} for the given {@link Format}.
- *
- * @param drmSessionManager The renderer's {@link DrmSessionManager}.
- * @param format The format, which has a video {@link Format#sampleMimeType}.
- * @return The {@link Capabilities} for this {@link Format}.
- * @see RendererCapabilities#supportsFormat(Format)
- */
- @Capabilities
- protected abstract int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format);
-
- /**
- * Creates a decoder for the given format.
- *
- * @param format The format for which a decoder is required.
- * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
- * May be null and can be ignored if decoder does not handle encrypted content.
- * @return The decoder.
- * @throws VideoDecoderException If an error occurred creating a suitable decoder.
- */
- protected abstract SimpleDecoder<
- VideoDecoderInputBuffer,
- ? extends VideoDecoderOutputBuffer,
- ? extends VideoDecoderException>
- createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
- throws VideoDecoderException;
-
- /**
- * Renders the specified output buffer.
- *
- * <p>The implementation of this method takes ownership of the output buffer and is responsible
- * for calling {@link VideoDecoderOutputBuffer#release()} either immediately or in the future.
- *
- * @param outputBuffer {@link VideoDecoderOutputBuffer} to render.
- * @param presentationTimeUs Presentation time in microseconds.
- * @param outputFormat Output {@link Format}.
- * @throws VideoDecoderException If an error occurs when rendering the output buffer.
- */
- protected void renderOutputBuffer(
- VideoDecoderOutputBuffer outputBuffer, long presentationTimeUs, Format outputFormat)
- throws VideoDecoderException {
- lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000);
- int bufferMode = outputBuffer.mode;
- boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && surface != null;
- boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null;
- if (!renderYuv && !renderSurface) {
- dropOutputBuffer(outputBuffer);
- } else {
- maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height);
- if (renderYuv) {
- outputBufferRenderer.setOutputBuffer(outputBuffer);
- } else {
- renderOutputBufferToSurface(outputBuffer, surface);
- }
- consecutiveDroppedFrameCount = 0;
- decoderCounters.renderedOutputBufferCount++;
- maybeNotifyRenderedFirstFrame();
- }
- }
-
- /**
- * Renders the specified output buffer to the passed surface.
- *
- * <p>The implementation of this method takes ownership of the output buffer and is responsible
- * for calling {@link VideoDecoderOutputBuffer#release()} either immediately or in the future.
- *
- * @param outputBuffer {@link VideoDecoderOutputBuffer} to render.
- * @param surface Output {@link Surface}.
- * @throws VideoDecoderException If an error occurs when rendering the output buffer.
- */
- protected abstract void renderOutputBufferToSurface(
- VideoDecoderOutputBuffer outputBuffer, Surface surface) throws VideoDecoderException;
-
- /**
- * Sets output surface.
- *
- * @param surface Surface.
- */
- protected final void setOutputSurface(@Nullable Surface surface) {
- if (this.surface != surface) {
- // The output has changed.
- this.surface = surface;
- if (surface != null) {
- outputBufferRenderer = null;
- outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV;
- if (decoder != null) {
- setDecoderOutputMode(outputMode);
- }
- onOutputChanged();
- } else {
- // The output has been removed. We leave the outputMode of the underlying decoder unchanged
- // in anticipation that a subsequent output will likely be of the same type.
- outputMode = C.VIDEO_OUTPUT_MODE_NONE;
- onOutputRemoved();
- }
- } else if (surface != null) {
- // The output is unchanged and non-null.
- onOutputReset();
- }
- }
-
- /**
- * Sets output buffer renderer.
- *
- * @param outputBufferRenderer Output buffer renderer.
- */
- protected final void setOutputBufferRenderer(
- @Nullable VideoDecoderOutputBufferRenderer outputBufferRenderer) {
- if (this.outputBufferRenderer != outputBufferRenderer) {
- // The output has changed.
- this.outputBufferRenderer = outputBufferRenderer;
- if (outputBufferRenderer != null) {
- surface = null;
- outputMode = C.VIDEO_OUTPUT_MODE_YUV;
- if (decoder != null) {
- setDecoderOutputMode(outputMode);
- }
- onOutputChanged();
- } else {
- // The output has been removed. We leave the outputMode of the underlying decoder unchanged
- // in anticipation that a subsequent output will likely be of the same type.
- outputMode = C.VIDEO_OUTPUT_MODE_NONE;
- onOutputRemoved();
- }
- } else if (outputBufferRenderer != null) {
- // The output is unchanged and non-null.
- onOutputReset();
- }
- }
-
- /**
- * Sets output mode of the decoder.
- *
- * @param outputMode Output mode.
- */
- protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
-
- // Internal methods.
-
- private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
- DrmSession.replaceSession(sourceDrmSession, session);
- sourceDrmSession = session;
- }
-
- private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
- DrmSession.replaceSession(decoderDrmSession, session);
- decoderDrmSession = session;
- }
-
- private void maybeInitDecoder() throws ExoPlaybackException {
- if (decoder != null) {
- return;
- }
-
- setDecoderDrmSession(sourceDrmSession);
-
- ExoMediaCrypto mediaCrypto = null;
- if (decoderDrmSession != null) {
- mediaCrypto = decoderDrmSession.getMediaCrypto();
- if (mediaCrypto == null) {
- DrmSessionException drmError = decoderDrmSession.getError();
- if (drmError != null) {
- // Continue for now. We may be able to avoid failure if the session recovers, or if a new
- // input format causes the session to be replaced before it's used.
- } else {
- // The drm session isn't open yet.
- return;
- }
- }
- }
-
- try {
- long decoderInitializingTimestamp = SystemClock.elapsedRealtime();
- decoder = createDecoder(inputFormat, mediaCrypto);
- setDecoderOutputMode(outputMode);
- long decoderInitializedTimestamp = SystemClock.elapsedRealtime();
- onDecoderInitialized(
- decoder.getName(),
- decoderInitializedTimestamp,
- decoderInitializedTimestamp - decoderInitializingTimestamp);
- decoderCounters.decoderInitCount++;
- } catch (VideoDecoderException e) {
- throw createRendererException(e, inputFormat);
- }
- }
-
- private boolean feedInputBuffer() throws VideoDecoderException, ExoPlaybackException {
- if (decoder == null
- || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
- || inputStreamEnded) {
- // We need to reinitialize the decoder or the input stream has ended.
- return false;
- }
-
- if (inputBuffer == null) {
- inputBuffer = decoder.dequeueInputBuffer();
- if (inputBuffer == null) {
- return false;
- }
- }
-
- if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
- inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
- decoder.queueInputBuffer(inputBuffer);
- inputBuffer = null;
- decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
- return false;
- }
-
- int result;
- FormatHolder formatHolder = getFormatHolder();
- if (waitingForKeys) {
- // We've already read an encrypted sample into buffer, and are waiting for keys.
- result = C.RESULT_BUFFER_READ;
- } else {
- result = readSource(formatHolder, inputBuffer, false);
- }
-
- if (result == C.RESULT_NOTHING_READ) {
- return false;
- }
- if (result == C.RESULT_FORMAT_READ) {
- onInputFormatChanged(formatHolder);
- return true;
- }
- if (inputBuffer.isEndOfStream()) {
- inputStreamEnded = true;
- decoder.queueInputBuffer(inputBuffer);
- inputBuffer = null;
- return false;
- }
- boolean bufferEncrypted = inputBuffer.isEncrypted();
- waitingForKeys = shouldWaitForKeys(bufferEncrypted);
- if (waitingForKeys) {
- return false;
- }
- if (waitingForFirstSampleInFormat) {
- formatQueue.add(inputBuffer.timeUs, inputFormat);
- waitingForFirstSampleInFormat = false;
- }
- inputBuffer.flip();
- inputBuffer.colorInfo = inputFormat.colorInfo;
- onQueueInputBuffer(inputBuffer);
- decoder.queueInputBuffer(inputBuffer);
- buffersInCodecCount++;
- decoderReceivedBuffers = true;
- decoderCounters.inputBufferCount++;
- inputBuffer = null;
- return true;
- }
-
- /**
- * Attempts to dequeue an output buffer from the decoder and, if successful, passes it to {@link
- * #processOutputBuffer(long, long)}.
- *
- * @param positionUs The player's current position.
- * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
- * measured at the start of the current iteration of the rendering loop.
- * @return Whether it may be possible to drain more output data.
- * @throws ExoPlaybackException If an error occurs draining the output buffer.
- */
- private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
- throws ExoPlaybackException, VideoDecoderException {
- if (outputBuffer == null) {
- outputBuffer = decoder.dequeueOutputBuffer();
- if (outputBuffer == null) {
- return false;
- }
- decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
- buffersInCodecCount -= outputBuffer.skippedOutputBufferCount;
- }
-
- if (outputBuffer.isEndOfStream()) {
- if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
- // We're waiting to re-initialize the decoder, and have now processed all final buffers.
- releaseDecoder();
- maybeInitDecoder();
- } else {
- outputBuffer.release();
- outputBuffer = null;
- outputStreamEnded = true;
- }
- return false;
- }
-
- boolean processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs);
- if (processedOutputBuffer) {
- onProcessedOutputBuffer(outputBuffer.timeUs);
- outputBuffer = null;
- }
- return processedOutputBuffer;
- }
-
- /**
- * Processes {@link #outputBuffer} by rendering it, skipping it or doing nothing, and returns
- * whether it may be possible to process another output buffer.
- *
- * @param positionUs The player's current position.
- * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
- * measured at the start of the current iteration of the rendering loop.
- * @return Whether it may be possible to drain another output buffer.
- * @throws ExoPlaybackException If an error occurs processing the output buffer.
- */
- private boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs)
- throws ExoPlaybackException, VideoDecoderException {
- if (initialPositionUs == C.TIME_UNSET) {
- initialPositionUs = positionUs;
- }
-
- long earlyUs = outputBuffer.timeUs - positionUs;
- if (!hasOutput()) {
- // Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
- if (isBufferLate(earlyUs)) {
- skipOutputBuffer(outputBuffer);
- return true;
- }
- return false;
- }
-
- long presentationTimeUs = outputBuffer.timeUs - outputStreamOffsetUs;
- Format format = formatQueue.pollFloor(presentationTimeUs);
- if (format != null) {
- outputFormat = format;
- }
-
- long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
- boolean isStarted = getState() == STATE_STARTED;
- if (!renderedFirstFrame
- || (isStarted
- && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) {
- renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
- return true;
- }
-
- if (!isStarted || positionUs == initialPositionUs) {
- return false;
- }
-
- if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs)
- && maybeDropBuffersToKeyframe(positionUs)) {
- return false;
- } else if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
- dropOutputBuffer(outputBuffer);
- return true;
- }
-
- if (earlyUs < 30000) {
- renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
- return true;
- }
-
- return false;
- }
-
- private boolean hasOutput() {
- return outputMode != C.VIDEO_OUTPUT_MODE_NONE;
- }
-
- private void onOutputChanged() {
- // If we know the video size, report it again immediately.
- maybeRenotifyVideoSizeChanged();
- // We haven't rendered to the new output yet.
- clearRenderedFirstFrame();
- if (getState() == STATE_STARTED) {
- setJoiningDeadlineMs();
- }
- }
-
- private void onOutputRemoved() {
- clearReportedVideoSize();
- clearRenderedFirstFrame();
- }
-
- private void onOutputReset() {
- // The output is unchanged and non-null. If we know the video size and/or have already
- // rendered to the output, report these again immediately.
- maybeRenotifyVideoSizeChanged();
- maybeRenotifyRenderedFirstFrame();
- }
-
- private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
- if (decoderDrmSession == null
- || (!bufferEncrypted
- && (playClearSamplesWithoutKeys || decoderDrmSession.playClearSamplesWithoutKeys()))) {
- return false;
- }
- @DrmSession.State int drmSessionState = decoderDrmSession.getState();
- if (drmSessionState == DrmSession.STATE_ERROR) {
- throw createRendererException(decoderDrmSession.getError(), inputFormat);
- }
- return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
- }
-
- private void setJoiningDeadlineMs() {
- joiningDeadlineMs =
- allowedJoiningTimeMs > 0
- ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs)
- : C.TIME_UNSET;
- }
-
- private void clearRenderedFirstFrame() {
- renderedFirstFrame = false;
- }
-
- private void maybeNotifyRenderedFirstFrame() {
- if (!renderedFirstFrame) {
- renderedFirstFrame = true;
- eventDispatcher.renderedFirstFrame(surface);
- }
- }
-
- private void maybeRenotifyRenderedFirstFrame() {
- if (renderedFirstFrame) {
- eventDispatcher.renderedFirstFrame(surface);
- }
- }
-
- private void clearReportedVideoSize() {
- reportedWidth = Format.NO_VALUE;
- reportedHeight = Format.NO_VALUE;
- }
-
- private void maybeNotifyVideoSizeChanged(int width, int height) {
- if (reportedWidth != width || reportedHeight != height) {
- reportedWidth = width;
- reportedHeight = height;
- eventDispatcher.videoSizeChanged(
- width, height, /* unappliedRotationDegrees= */ 0, /* pixelWidthHeightRatio= */ 1);
- }
- }
-
- private void maybeRenotifyVideoSizeChanged() {
- if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
- eventDispatcher.videoSizeChanged(
- reportedWidth,
- reportedHeight,
- /* unappliedRotationDegrees= */ 0,
- /* pixelWidthHeightRatio= */ 1);
- }
- }
-
- private void maybeNotifyDroppedFrames() {
- if (droppedFrames > 0) {
- long now = SystemClock.elapsedRealtime();
- long elapsedMs = now - droppedFrameAccumulationStartTimeMs;
- eventDispatcher.droppedFrames(droppedFrames, elapsedMs);
- droppedFrames = 0;
- droppedFrameAccumulationStartTimeMs = now;
- }
- }
-
- private static boolean isBufferLate(long earlyUs) {
- // Class a buffer as late if it should have been presented more than 30 ms ago.
- return earlyUs < -30000;
- }
-
- private static boolean isBufferVeryLate(long earlyUs) {
- // Class a buffer as very late if it should have been presented more than 500 ms ago.
- return earlyUs < -500000;
- }
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderException.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderException.java
deleted file mode 100644
index 68108af..0000000
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderException.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.video;
-
-/** Thrown when a video decoder error occurs. */
-public class VideoDecoderException extends Exception {
-
- /**
- * Creates an instance with the given message.
- *
- * @param message The detail message for this exception.
- */
- public VideoDecoderException(String message) {
- super(message);
- }
-
- /**
- * Creates an instance with the given message and cause.
- *
- * @param message The detail message for this exception.
- * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
- * A <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or unknown.
- */
- public VideoDecoderException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java
index 457aa30..e78a511 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoDecoderOutputBuffer.java
@@ -23,17 +23,6 @@
/** Video decoder output buffer containing video frame data. */
public class VideoDecoderOutputBuffer extends OutputBuffer {
- /** Buffer owner. */
- public interface Owner {
-
- /**
- * Releases the buffer.
- *
- * @param outputBuffer Output buffer.
- */
- void releaseOutputBuffer(VideoDecoderOutputBuffer outputBuffer);
- }
-
// LINT.IfChange
public static final int COLORSPACE_UNKNOWN = 0;
public static final int COLORSPACE_BT601 = 1;
@@ -68,14 +57,14 @@
*/
@Nullable public ByteBuffer supplementalData;
- private final Owner owner;
+ private final Owner<VideoDecoderOutputBuffer> owner;
/**
* Creates VideoDecoderOutputBuffer.
*
* @param owner Buffer owner.
*/
- public VideoDecoderOutputBuffer(Owner owner) {
+ public VideoDecoderOutputBuffer(Owner<VideoDecoderOutputBuffer> owner) {
this.owner = owner;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java
index 746903a..bc275f1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameMetadataListener.java
@@ -19,14 +19,14 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
-/** A listener for metadata corresponding to video frame being rendered. */
+/** A listener for metadata corresponding to video frames being rendered. */
public interface VideoFrameMetadataListener {
/**
- * Called when the video frame about to be rendered. This method is called on the playback thread.
+ * Called on the playback thread when a video frame is about to be rendered.
*
- * @param presentationTimeUs The presentation time of the output buffer, in microseconds.
+ * @param presentationTimeUs The presentation time of the frame, in microseconds.
* @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds.
- * If the platform API version of the device is less than 21, then this is the best effort.
+ * If the platform API version of the device is less than 21, then this is a best effort.
* @param format The format associated with the frame.
* @param mediaFormat The framework media format associated with the frame, or {@code null} if not
* known or not applicable (e.g., because the frame was not output by a {@link
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java
index bf31ce2..2134772 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java
@@ -26,6 +26,7 @@
import android.view.Display;
import android.view.WindowManager;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
@@ -40,9 +41,9 @@
private static final long VSYNC_OFFSET_PERCENTAGE = 80;
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
- private final WindowManager windowManager;
- private final VSyncSampler vsyncSampler;
- private final DefaultDisplayListener displayListener;
+ @Nullable private final WindowManager windowManager;
+ @Nullable private final VSyncSampler vsyncSampler;
+ @Nullable private final DefaultDisplayListener displayListener;
private long vsyncDurationNs;
private long vsyncOffsetNs;
@@ -88,9 +89,8 @@
vsyncOffsetNs = C.TIME_UNSET;
}
- /**
- * Enables the helper. Must be called from the playback thread.
- */
+ /** Enables the helper. Must be called from the playback thread. */
+ @TargetApi(17) // displayListener is null if Util.SDK_INT < 17.
public void enable() {
haveSync = false;
if (windowManager != null) {
@@ -102,9 +102,8 @@
}
}
- /**
- * Disables the helper. Must be called from the playback thread.
- */
+ /** Disables the helper. Must be called from the playback thread. */
+ @TargetApi(17) // displayListener is null if Util.SDK_INT < 17.
public void disable() {
if (windowManager != null) {
if (displayListener != null) {
@@ -187,7 +186,7 @@
return snappedTimeNs - vsyncOffsetNs;
}
- @TargetApi(17)
+ @RequiresApi(17)
private DefaultDisplayListener maybeBuildDefaultDisplayListenerV17(Context context) {
DisplayManager manager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
return manager == null ? null : new DefaultDisplayListener(manager);
@@ -226,7 +225,7 @@
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
}
- @TargetApi(17)
+ @RequiresApi(17)
private final class DefaultDisplayListener implements DisplayManager.DisplayListener {
private final DisplayManager displayManager;
@@ -288,7 +287,7 @@
private VSyncSampler() {
sampledVsyncTimeNs = C.TIME_UNSET;
- choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler");
+ choreographerOwnerThread = new HandlerThread("ExoPlayer:FrameReleaseChoreographer");
choreographerOwnerThread.start();
handler = Util.createHandler(choreographerOwnerThread.getLooper(), /* callback= */ this);
handler.sendEmptyMessage(CREATE_CHOREOGRAPHER);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoListener.java
index 6f492c3..948c388 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoListener.java
@@ -52,7 +52,7 @@
/**
* Called when a frame is rendered for the first time since setting the surface, and when a frame
- * is rendered for the first time since a video track was selected.
+ * is rendered for the first time since the renderer was reset.
*/
default void onRenderedFirstFrame() {}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
index e7dfd12..671d66c 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
@@ -72,6 +72,28 @@
default void onDroppedFrames(int count, long elapsedMs) {}
/**
+ * Called to report the video processing offset of video frames processed by the video renderer.
+ *
+ * <p>Video processing offset represents how early a video frame is processed compared to the
+ * player's current position. For each video frame, the offset is calculated as <em>P<sub>vf</sub>
+ * - P<sub>pl</sub></em> where <em>P<sub>vf</sub></em> is the presentation timestamp of the video
+ * frame and <em>P<sub>pl</sub></em> is the current position of the player. Positive values
+ * indicate the frame was processed early enough whereas negative values indicate that the
+ * player's position had progressed beyond the frame's timestamp when the frame was processed (and
+ * the frame was probably dropped).
+ *
+ * <p>The renderer reports the sum of video processing offset samples (one sample per processed
+ * video frame: dropped, skipped or rendered) and the total number of samples.
+ *
+ * @param totalProcessingOffsetUs The sum of all video frame processing offset samples for the
+ * video frames processed by the renderer in microseconds.
+ * @param frameCount The number of samples included in the {@code totalProcessingOffsetUs}.
+ * @param format The {@link Format} that is currently output.
+ */
+ default void onVideoFrameProcessingOffset(
+ long totalProcessingOffsetUs, int frameCount, Format format) {}
+
+ /**
* Called before a frame is rendered for the first time since setting the surface, and each time
* there's a change in the size, rotation or pixel aspect ratio of the video being rendered.
*
@@ -159,6 +181,17 @@
}
}
+ /** Invokes {@link VideoRendererEventListener#onVideoFrameProcessingOffset}. */
+ public void reportVideoFrameProcessingOffset(
+ long totalProcessingOffsetUs, int frameCount, Format format) {
+ if (handler != null) {
+ handler.post(
+ () ->
+ castNonNull(listener)
+ .onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount, format));
+ }
+ }
+
/** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. */
public void videoSizeChanged(
int width,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java
index 0333b0b..abf08f3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java
@@ -24,6 +24,7 @@
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@@ -32,6 +33,7 @@
/** A {@link Renderer} that parses the camera motion track. */
public class CameraMotionRenderer extends BaseRenderer {
+ private static final String TAG = "CameraMotionRenderer";
// The amount of time to read samples ahead of the current time.
private static final int SAMPLE_WINDOW_DURATION_US = 100000;
@@ -49,6 +51,11 @@
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
@Capabilities
public int supportsFormat(Format format) {
return MimeTypes.APPLICATION_CAMERA_MOTION.equals(format.sampleMimeType)
@@ -86,6 +93,7 @@
while (!hasReadStreamToEnd() && lastTimestampUs < positionUs + SAMPLE_WINDOW_DURATION_US) {
buffer.clear();
FormatHolder formatHolder = getFormatHolder();
+ @SampleStream.ReadDataResult
int result = readSource(formatHolder, buffer, /* formatRequired= */ false);
if (result != C.RESULT_BUFFER_READ || buffer.isEndOfStream()) {
return;
diff --git a/tree/library/core/src/test/assets/binary/1024_incrementing_bytes.mp3 b/tree/library/core/src/test/assets/binary/1024_incrementing_bytes.mp3
deleted file mode 100644
index c8b49c8..0000000
--- a/tree/library/core/src/test/assets/binary/1024_incrementing_bytes.mp3
+++ /dev/null
Binary files differ
diff --git a/tree/library/core/src/test/assets/subrip/typical b/tree/library/core/src/test/assets/subrip/typical
deleted file mode 100644
index 1331f75..0000000
--- a/tree/library/core/src/test/assets/subrip/typical
+++ /dev/null
@@ -1,12 +0,0 @@
-1
-00:00:00,000 --> 00:00:01,234
-This is the first subtitle.
-
-2
-00:00:02,345 --> 00:00:03,456
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_extra_blank_line b/tree/library/core/src/test/assets/subrip/typical_extra_blank_line
deleted file mode 100644
index f5882a1..0000000
--- a/tree/library/core/src/test/assets/subrip/typical_extra_blank_line
+++ /dev/null
@@ -1,13 +0,0 @@
-1
-00:00:00,000 --> 00:00:01,234
-This is the first subtitle.
-
-
-2
-00:00:02,345 --> 00:00:03,456
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_missing_sequence b/tree/library/core/src/test/assets/subrip/typical_missing_sequence
deleted file mode 100644
index 56d49ac..0000000
--- a/tree/library/core/src/test/assets/subrip/typical_missing_sequence
+++ /dev/null
@@ -1,11 +0,0 @@
-1
-00:00:00,000 --> 00:00:01,234
-This is the first subtitle.
-
-00:00:02,345 --> 00:00:03,456
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_missing_timecode b/tree/library/core/src/test/assets/subrip/typical_missing_timecode
deleted file mode 100644
index cd25ffc..0000000
--- a/tree/library/core/src/test/assets/subrip/typical_missing_timecode
+++ /dev/null
@@ -1,19 +0,0 @@
-1
-00:00:00,000 --> 00:00:01,234
-This is the first subtitle.
-
-2
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
-
-4
- --> 00:00:10,901
-This is the fourth subtitle.
-
-5
-00:00:12,901 -->
-This is the fifth subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_negative_timestamps b/tree/library/core/src/test/assets/subrip/typical_negative_timestamps
deleted file mode 100644
index 2a47c09..0000000
--- a/tree/library/core/src/test/assets/subrip/typical_negative_timestamps
+++ /dev/null
@@ -1,12 +0,0 @@
-1
--0:00:04,567 --> -0:00:03,456
-This is the first subtitle.
-
-2
--00:00:02,345 --> 00:00:01,234
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_with_byte_order_mark b/tree/library/core/src/test/assets/subrip/typical_with_byte_order_mark
deleted file mode 100644
index 4f5b32f..0000000
--- a/tree/library/core/src/test/assets/subrip/typical_with_byte_order_mark
+++ /dev/null
@@ -1,12 +0,0 @@
-1
-00:00:00,000 --> 00:00:01,234
-This is the first subtitle.
-
-2
-00:00:02,345 --> 00:00:03,456
-This is the second subtitle.
-Second subtitle with second line.
-
-3
-00:00:04,567 --> 00:00:08,901
-This is the third subtitle.
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java
index 9a44d6d..2b9f476 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.robolectric.annotation.Config.TARGET_SDK;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.content.Context;
import android.media.AudioFocusRequest;
@@ -36,9 +37,11 @@
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowAudioManager;
/** Unit tests for {@link AudioFocusManager}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public class AudioFocusManagerTest {
private static final int NO_COMMAND_RECEIVED = ~PLAYER_COMMAND_WAIT_FOR_CALLBACK;
@@ -64,14 +67,11 @@
@Test
public void setAudioAttributes_withNullUsage_doesNotManageAudioFocus() {
- // Ensure that NULL audio attributes -> don't manage audio focus
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE))
+ audioFocusManager.setAudioAttributes(/* audioAttributes= */ null);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
.isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY))
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
ShadowAudioManager.AudioFocusRequest request =
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
@@ -80,23 +80,21 @@
@Test
@Config(maxSdk = 25)
- public void setAudioAttributes_withNullUsage_releasesAudioFocus() {
- // Create attributes and request audio focus.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
+ public void setAudioAttributes_withNullUsage_abandonsAudioFocus() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
ShadowAudioManager.AudioFocusRequest request =
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
- // Ensure that setting null audio attributes with audio focus releases audio focus.
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY))
+ // Ensure that setting null audio attributes with focus releases focus.
+ audioFocusManager.setAudioAttributes(/* audioAttributes= */ null);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
AudioManager.OnAudioFocusChangeListener lastRequest =
Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener();
@@ -105,23 +103,20 @@
@Test
@Config(minSdk = 26, maxSdk = TARGET_SDK)
- public void setAudioAttributes_withNullUsage_releasesAudioFocus_v26() {
- // Create attributes and request audio focus.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
+ public void setAudioAttributes_withNullUsage_abandonsAudioFocus_v26() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
ShadowAudioManager.AudioFocusRequest request =
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
- // Ensure that setting null audio attributes with audio focus releases audio focus.
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY))
+ // Ensure that setting null audio attributes with focus releases focus.
+ audioFocusManager.setAudioAttributes(/* audioAttributes= */ null);
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
AudioFocusRequest lastRequest =
Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest();
@@ -130,10 +125,10 @@
@Test
public void setAudioAttributes_withUsageAlarm_throwsIllegalArgumentException() {
- // Ensure that audio attributes that map to AUDIOFOCUS_GAIN_TRANSIENT* throw
+ // USAGE_ALARM attributes map to AUDIOFOCUS_GAIN_TRANSIENT, which should result in failure.
AudioAttributes alarm = new AudioAttributes.Builder().setUsage(C.USAGE_ALARM).build();
try {
- audioFocusManager.setAudioAttributes(alarm, /* playWhenReady= */ false, Player.STATE_IDLE);
+ audioFocusManager.setAudioAttributes(alarm);
fail();
} catch (IllegalArgumentException e) {
// Expected
@@ -142,14 +137,14 @@
@Test
public void setAudioAttributes_withUsageMedia_usesAudioFocusGain() {
- // Ensure setting media type audio attributes requests AUDIOFOCUS_GAIN.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ AudioAttributes mediaAudioAttributes =
+ new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
+ audioFocusManager.setAudioAttributes(mediaAudioAttributes);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
ShadowAudioManager.AudioFocusRequest request =
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
@@ -157,15 +152,12 @@
}
@Test
- public void setAudioAttributes_inStateEnded_requestsAudioFocus() {
- // Ensure setting audio attributes when player is in STATE_ENDED requests audio focus.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
+ public void setAudioAttributes_inEndedState_requestsAudioFocus() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_ENDED))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_ENDED))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
ShadowAudioManager.AudioFocusRequest request =
Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
@@ -173,61 +165,216 @@
}
@Test
- public void handlePrepare_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady() {
- // Ensure that when playWhenReady is true while the player is IDLE, audio focus is only
- // requested after calling handlePrepare.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
-
+ public void updateAudioFocus_idleToBuffering_setsPlayerCommandPlayWhenReady() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_IDLE))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
- assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ true))
+ assertThat(
+ audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_BUFFERING))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
+ }
+
+ @Test
+ public void updateAudioFocus_pausedToPlaying_setsPlayerCommandPlayWhenReady() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ // Audio focus should not be requested yet, because playWhenReady is false.
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
+
+ // Audio focus should be requested now that playWhenReady is true.
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
+ }
+
+ @Test
+ public void updateAudioFocus_pausedToPlaying_withTransientLoss_setsPlayerCommandPlayWhenReady() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+
+ // Simulate transient focus loss.
+ audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+ // Focus should be re-requested rather than staying in a state of transient focus loss. See
+ // https://github.com/google/ExoPlayer/issues/7182 for context.
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
}
@Test
- public void handleSetPlayWhenReady_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady() {
- // Ensure that audio focus is not requested until playWhenReady is true.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
-
+ public void updateAudioFocus_pausedToPlaying_withTransientDuck_setsPlayerCommandPlayWhenReady() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
- assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ false))
- .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ false, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull();
- assertThat(
- audioFocusManager.handleSetPlayWhenReady(/* playWhenReady= */ true, Player.STATE_READY))
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+
+ // Simulate transient ducking.
+ audioFocusManager
+ .getFocusListener()
+ .onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+ assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
+
+ // Focus should be re-requested, rather than staying in a state of transient ducking. This
+ // should restore the volume to 1.0. See https://github.com/google/ExoPlayer/issues/7182 for
+ // context.
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
}
@Test
- public void onAudioFocusChange_withDuckEnabled_volumeReducedAndRestored() {
- // Ensure that the volume multiplier is adjusted when audio focus is lost to
- // AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, and returns to the default value after focus is
- // regained.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
-
+ public void updateAudioFocus_abandonFocusWhenDucked_restoresFullVolume() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+
+ // Simulate transient ducking.
+ audioFocusManager
+ .getFocusListener()
+ .onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+ assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
+
+ // Configure the manager to no longer handle focus.
+ audioFocusManager.setAudioAttributes(null);
+
+ // Focus should be abandoned, which should restore the volume to 1.0.
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f);
+ }
+
+ @Test
+ @Config(maxSdk = 25)
+ public void updateAudioFocus_readyToIdle_abandonsAudioFocus() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
+
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener())
+ .isEqualTo(request.listener);
+ }
+
+ @Test
+ @Config(minSdk = 26, maxSdk = TARGET_SDK)
+ public void updateAudioFocus_readyToIdle_abandonsAudioFocus_v26() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
+
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest())
+ .isEqualTo(request.audioFocusRequest);
+ }
+
+ @Test
+ @Config(maxSdk = 25)
+ public void updateAudioFocus_readyToIdle_withoutFocus_isNoOp() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(null);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(request).isNull();
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
+ .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
+ }
+
+ @Test
+ @Config(minSdk = 26, maxSdk = TARGET_SDK)
+ public void updateAudioFocus_readyToIdle_withoutFocus_isNoOp_v26() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(null);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
+ ShadowAudioManager.AudioFocusRequest request =
+ Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
+ assertThat(request).isNull();
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE))
+ .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
+ assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
+ }
+
+ @Test
+ public void release_doesNotCallPlayerControlToRestoreVolume() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
+ .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
+
+ // Simulate transient ducking.
+ audioFocusManager
+ .getFocusListener()
+ .onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+ assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
+
+ audioFocusManager.release();
+
+ // PlaybackController.setVolumeMultiplier should not have been called to restore the volume.
+ assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
+ }
+
+ @Test
+ public void onAudioFocusChange_withDuckEnabled_reducesAndRestoresVolume() {
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
audioFocusManager
.getFocusListener()
.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+
assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f);
assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(NO_COMMAND_RECEIVED);
audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN);
@@ -236,19 +383,17 @@
@Test
public void onAudioFocusChange_withPausedWhenDucked_sendsCommandWaitForCallback() {
- // Ensure that the player is commanded to pause when audio focus is lost with
- // AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK and the content type is CONTENT_TYPE_SPEECH.
- AudioAttributes media =
+ Shadows.shadowOf(audioManager)
+ .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ AudioAttributes speechAudioAttributes =
new AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_SPEECH)
.build();
+ audioFocusManager.setAudioAttributes(speechAudioAttributes);
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
audioFocusManager
@@ -261,16 +406,12 @@
}
@Test
- public void onAudioFocusChange_withTransientLost_sendsCommandWaitForCallback() {
- // Ensure that the player is commanded to pause when audio focus is lost with
- // AUDIOFOCUS_LOSS_TRANSIENT.
- AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build();
-
+ public void onAudioFocusChange_withTransientLoss_sendsCommandWaitForCallback() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
@@ -280,20 +421,12 @@
@Test
@Config(maxSdk = 25)
- public void onAudioFocusChange_withAudioFocusLost_sendsDoNotPlayAndAbandondsFocus() {
- // Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
- // focus.
- AudioAttributes media =
- new AudioAttributes.Builder()
- .setUsage(C.USAGE_MEDIA)
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .build();
-
+ public void onAudioFocusChange_withFocusLoss_sendsDoNotPlayAndAbandonsFocus() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
@@ -307,20 +440,12 @@
@Test
@Config(minSdk = 26, maxSdk = TARGET_SDK)
- public void onAudioFocusChange_withAudioFocusLost_sendsDoNotPlayAndAbandondsFocus_v26() {
- // Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and abandon audio
- // focus.
- AudioAttributes media =
- new AudioAttributes.Builder()
- .setUsage(C.USAGE_MEDIA)
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .build();
-
+ public void onAudioFocusChange_withFocusLoss_sendsDoNotPlayAndAbandonsFocus_v26() {
Shadows.shadowOf(audioManager)
.setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
+ audioFocusManager.setAudioAttributes(AudioAttributes.DEFAULT);
+
+ assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY))
.isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
@@ -330,120 +455,6 @@
.isEqualTo(Shadows.shadowOf(audioManager).getLastAudioFocusRequest().audioFocusRequest);
}
- @Test
- @Config(maxSdk = 25)
- public void handleStop_withAudioFocus_abandonsAudioFocus() {
- // Ensure that handleStop causes AudioFocusManager to abandon audio focus.
- AudioAttributes media =
- new AudioAttributes.Builder()
- .setUsage(C.USAGE_MEDIA)
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .build();
-
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
-
- ShadowAudioManager.AudioFocusRequest request =
- Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
- audioFocusManager.handleStop();
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener())
- .isEqualTo(request.listener);
- }
-
- @Test
- @Config(minSdk = 26, maxSdk = TARGET_SDK)
- public void handleStop_withAudioFocus_abandonsAudioFocus_v26() {
- // Ensure that handleStop causes AudioFocusManager to abandon audio focus.
- AudioAttributes media =
- new AudioAttributes.Builder()
- .setUsage(C.USAGE_MEDIA)
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .build();
-
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ true, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY);
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
-
- ShadowAudioManager.AudioFocusRequest request =
- Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
- audioFocusManager.handleStop();
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest())
- .isEqualTo(request.audioFocusRequest);
- }
-
- @Test
- @Config(maxSdk = 25)
- public void handleStop_withoutAudioFocus_stillAbandonsFocus() {
- // Ensure that handleStop causes AudioFocusManager to call through to abandon audio focus
- // even if focus wasn't requested.
- AudioAttributes media =
- new AudioAttributes.Builder()
- .setUsage(C.USAGE_MEDIA)
- .setContentType(C.CONTENT_TYPE_SPEECH)
- .build();
-
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- media, /* playWhenReady= */ false, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
- ShadowAudioManager.AudioFocusRequest request =
- Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
- assertThat(request).isNull();
-
- audioFocusManager.handleStop();
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNotNull();
- }
-
- @Test
- @Config(maxSdk = 25)
- public void handleStop_withoutHandlingAudioFocus_isNoOp() {
- // Ensure that handleStop is a no-op if audio focus isn't handled.
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
- ShadowAudioManager.AudioFocusRequest request =
- Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
- assertThat(request).isNull();
-
- audioFocusManager.handleStop();
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull();
- }
-
- @Test
- @Config(minSdk = 26, maxSdk = TARGET_SDK)
- public void handleStop_withoutHandlingAudioFocus_isNoOp_v26() {
- // Ensure that handleStop is a no-op if audio focus isn't handled.
- Shadows.shadowOf(audioManager)
- .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- assertThat(
- audioFocusManager.setAudioAttributes(
- /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY))
- .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY);
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
- ShadowAudioManager.AudioFocusRequest request =
- Shadows.shadowOf(audioManager).getLastAudioFocusRequest();
- assertThat(request).isNull();
-
- audioFocusManager.handleStop();
- assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull();
- }
-
private int getAudioFocusGainFromRequest(ShadowAudioManager.AudioFocusRequest audioFocusRequest) {
return Util.SDK_INT >= 26
? audioFocusRequest.audioFocusRequest.getFocusGain()
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java
index 2222d1a..8ed0b21 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java
@@ -19,6 +19,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.DefaultLoadControl.Builder;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import org.junit.Before;
import org.junit.Test;
@@ -29,8 +31,8 @@
public class DefaultLoadControlTest {
private static final float SPEED = 1f;
- private static final long MIN_BUFFER_US = C.msToUs(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS);
private static final long MAX_BUFFER_US = C.msToUs(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS);
+ private static final long MIN_BUFFER_US = MAX_BUFFER_US / 2;
private static final int TARGET_BUFFER_BYTES = C.DEFAULT_BUFFER_SEGMENT_SIZE * 2;
private Builder builder;
@@ -44,42 +46,51 @@
}
@Test
- public void testShouldContinueLoading_untilMaxBufferExceeded() {
+ public void shouldContinueLoading_untilMaxBufferExceeded() {
createDefaultLoadControl();
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue();
- assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isTrue();
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isTrue();
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
}
@Test
- public void testShouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() {
+ public void shouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() {
+ builder.setBufferDurationsMs(
+ /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US),
+ /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US),
+ /* bufferForPlaybackMs= */ 0,
+ /* bufferForPlaybackAfterRebufferMs= */ 0);
createDefaultLoadControl();
- assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
+ assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isFalse();
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isTrue();
}
@Test
- public void
- testContinueLoadingOnceBufferingStopped_andBufferAlmostEmpty_evenIfMinBufferNotReached() {
+ public void continueLoadingOnceBufferingStopped_andBufferAlmostEmpty_evenIfMinBufferNotReached() {
builder.setBufferDurationsMs(
/* minBufferMs= */ 0,
/* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US),
/* bufferForPlaybackMs= */ 0,
/* bufferForPlaybackAfterRebufferMs= */ 0);
createDefaultLoadControl();
- assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
+ assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
assertThat(loadControl.shouldContinueLoading(5 * C.MICROS_PER_SECOND, SPEED)).isFalse();
assertThat(loadControl.shouldContinueLoading(500L, SPEED)).isTrue();
}
@Test
- public void testShouldContinueLoadingWithTargetBufferBytesReached_untilMinBufferReached() {
+ public void shouldContinueLoadingWithTargetBufferBytesReached_untilMinBufferReached() {
+ builder.setPrioritizeTimeOverSizeThresholds(true);
+ builder.setBufferDurationsMs(
+ /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US),
+ /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US),
+ /* bufferForPlaybackMs= */ 0,
+ /* bufferForPlaybackAfterRebufferMs= */ 0);
createDefaultLoadControl();
makeSureTargetBufferBytesReached();
@@ -90,9 +101,11 @@
}
@Test
- public void testShouldNeverContinueLoading_ifMaxBufferReachedAndNotPrioritizeTimeOverSize() {
+ public void
+ shouldContinueLoading_withTargetBufferBytesReachedAndNotPrioritizeTimeOverSize_returnsTrueAsSoonAsTargetBufferReached() {
builder.setPrioritizeTimeOverSizeThresholds(false);
createDefaultLoadControl();
+
// Put loadControl in buffering state.
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue();
makeSureTargetBufferBytesReached();
@@ -104,7 +117,12 @@
}
@Test
- public void testShouldContinueLoadingWithMinBufferReached_inFastPlayback() {
+ public void shouldContinueLoadingWithMinBufferReached_inFastPlayback() {
+ builder.setBufferDurationsMs(
+ /* minBufferMs= */ (int) C.usToMs(MIN_BUFFER_US),
+ /* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US),
+ /* bufferForPlaybackMs= */ 0,
+ /* bufferForPlaybackAfterRebufferMs= */ 0);
createDefaultLoadControl();
// At normal playback speed, we stop buffering when the buffer reaches the minimum.
@@ -114,7 +132,7 @@
}
@Test
- public void testShouldNotContinueLoadingWithMaxBufferReached_inFastPlayback() {
+ public void shouldNotContinueLoadingWithMaxBufferReached_inFastPlayback() {
createDefaultLoadControl();
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, /* playbackSpeed= */ 100f))
@@ -122,15 +140,25 @@
}
@Test
- public void testStartsPlayback_whenMinBufferSizeReached() {
+ public void startsPlayback_whenMinBufferSizeReached() {
createDefaultLoadControl();
+
assertThat(loadControl.shouldStartPlayback(MIN_BUFFER_US, SPEED, /* rebuffering= */ false))
.isTrue();
}
+ @Test
+ public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() {
+ loadControl = builder.createDefaultLoadControl();
+ loadControl.onTracksSelected(new Renderer[0], TrackGroupArray.EMPTY, new TrackSelectionArray());
+
+ assertThat(
+ loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, /* playbackSpeed= */ 1f))
+ .isTrue();
+ }
+
private void createDefaultLoadControl() {
- builder.setAllocator(allocator);
- builder.setTargetBufferBytes(TARGET_BUFFER_BYTES);
+ builder.setAllocator(allocator).setTargetBufferBytes(TARGET_BUFFER_BYTES);
loadControl = builder.createDefaultLoadControl();
loadControl.onTracksSelected(new Renderer[0], null, null);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
index b6e3d7a..217df76 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
@@ -22,7 +22,7 @@
import static org.mockito.MockitoAnnotations.initMocks;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener;
+import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import org.junit.Before;
@@ -36,10 +36,9 @@
private static final long TEST_POSITION_US = 123456789012345678L;
private static final long SLEEP_TIME_MS = 1_000;
- private static final PlaybackParameters TEST_PLAYBACK_PARAMETERS =
- new PlaybackParameters(/* speed= */ 2f);
+ private static final float TEST_PLAYBACK_SPEED = 2f;
- @Mock private PlaybackParameterListener listener;
+ @Mock private PlaybackSpeedListener listener;
private FakeClock fakeClock;
private DefaultMediaClock mediaClock;
@@ -110,119 +109,117 @@
}
@Test
- public void standaloneGetPlaybackParameters_initializedWithDefaultPlaybackParameters() {
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
+ public void standaloneGetPlaybackSpeed_initializedWithDefaultPlaybackSpeed() {
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
}
@Test
- public void standaloneSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
+ public void standaloneSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
}
@Test
- public void standaloneSetPlaybackParameters_shouldNotTriggerCallback() {
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ public void standaloneSetPlaybackSpeed_shouldNotTriggerCallback() {
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
verifyNoMoreInteractions(listener);
}
@Test
- public void standaloneSetPlaybackParameters_shouldApplyNewPlaybackSpeed() {
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ public void standaloneSetPlaybackSpeed_shouldApplyNewPlaybackSpeed() {
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
mediaClock.start();
- // Asserts that clock is running with speed declared in getPlaybackParameters().
+ // Asserts that clock is running with speed declared in getPlaybackSpeed().
assertClockIsRunning(/* isReadingAhead= */ false);
}
@Test
- public void standaloneSetOtherPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
- mediaClock.setPlaybackParameters(PlaybackParameters.DEFAULT);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
+ public void standaloneSetOtherPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ mediaClock.setPlaybackSpeed(Player.DEFAULT_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
}
@Test
- public void enableRendererMediaClock_shouldOverwriteRendererPlaybackParametersIfPossible()
+ public void enableRendererMediaClock_shouldOverwriteRendererPlaybackSpeedIfPossible()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ true);
+ new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
verifyNoMoreInteractions(listener);
}
@Test
- public void enableRendererMediaClockWithFixedParameters_usesRendererPlaybackParameters()
+ public void enableRendererMediaClockWithFixedPlaybackSpeed_usesRendererPlaybackSpeed()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
}
@Test
- public void enableRendererMediaClockWithFixedParameters_shouldTriggerCallback()
+ public void enableRendererMediaClockWithFixedPlaybackSpeed_shouldTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
+ verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
}
@Test
- public void enableRendererMediaClockWithFixedButSamePlaybackParameters_shouldNotTriggerCallback()
+ public void enableRendererMediaClockWithFixedButSamePlaybackSpeed_shouldNotTriggerCallback()
throws ExoPlaybackException {
- FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
- /* playbackParametersAreMutable= */ false);
+ FakeMediaClockRenderer mediaClockRenderer =
+ new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
verifyNoMoreInteractions(listener);
}
@Test
- public void disableRendererMediaClock_shouldKeepPlaybackParameters()
- throws ExoPlaybackException {
+ public void disableRendererMediaClock_shouldKeepPlaybackSpeed() throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
mediaClock.onRendererDisabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
}
@Test
- public void rendererClockSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue()
+ public void rendererClockSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue()
throws ExoPlaybackException {
- FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
- /* playbackParametersAreMutable= */ true);
+ FakeMediaClockRenderer mediaClockRenderer =
+ new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
}
@Test
- public void rendererClockSetPlaybackParameters_shouldNotTriggerCallback()
- throws ExoPlaybackException {
- FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
- /* playbackParametersAreMutable= */ true);
+ public void rendererClockSetPlaybackSpeed_shouldNotTriggerCallback() throws ExoPlaybackException {
+ FakeMediaClockRenderer mediaClockRenderer =
+ new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
verifyNoMoreInteractions(listener);
}
@Test
- public void rendererClockSetPlaybackParametersOverwrite_getParametersShouldReturnSameValue()
+ public void rendererClockSetPlaybackSpeedOverwrite_getPlaybackSpeedShouldReturnSameValue()
throws ExoPlaybackException {
- FakeMediaClockRenderer mediaClockRenderer = new MediaClockRenderer(PlaybackParameters.DEFAULT,
- /* playbackParametersAreMutable= */ false);
+ FakeMediaClockRenderer mediaClockRenderer =
+ new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
- assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
+ mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
}
@Test
@@ -266,16 +263,15 @@
}
@Test
- public void getPositionWithPlaybackParameterChange_shouldTriggerCallback()
+ public void getPositionWithPlaybackSpeedChange_shouldTriggerCallback()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(
- PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
+ new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
- // Silently change playback parameters of renderer clock.
- mediaClockRenderer.playbackParameters = TEST_PLAYBACK_PARAMETERS;
+ // Silently change playback speed of renderer clock.
+ mediaClockRenderer.playbackSpeed = TEST_PLAYBACK_SPEED;
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
+ verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
}
@Test
@@ -360,10 +356,9 @@
private void assertClockIsRunning(boolean isReadingAhead) {
long clockStartUs = mediaClock.syncAndGetPositionUs(isReadingAhead);
fakeClock.advanceTime(SLEEP_TIME_MS);
+ int scaledUsPerMs = Math.round(mediaClock.getPlaybackSpeed() * 1000f);
assertThat(mediaClock.syncAndGetPositionUs(isReadingAhead))
- .isEqualTo(
- clockStartUs
- + mediaClock.getPlaybackParameters().getMediaTimeUsForPlayoutTimeMs(SLEEP_TIME_MS));
+ .isEqualTo(clockStartUs + (SLEEP_TIME_MS * scaledUsPerMs));
}
private void assertClockIsStopped() {
@@ -376,34 +371,37 @@
@SuppressWarnings("HidingField")
private static class MediaClockRenderer extends FakeMediaClockRenderer {
- private final boolean playbackParametersAreMutable;
+ private final boolean playbackSpeedIsMutable;
private final boolean isReady;
private final boolean isEnded;
- public PlaybackParameters playbackParameters;
+ public float playbackSpeed;
public long positionUs;
public MediaClockRenderer() throws ExoPlaybackException {
- this(PlaybackParameters.DEFAULT, false, true, false, false);
+ this(Player.DEFAULT_PLAYBACK_SPEED, false, true, false, false);
}
- public MediaClockRenderer(PlaybackParameters playbackParameters,
- boolean playbackParametersAreMutable)
+ public MediaClockRenderer(float playbackSpeed, boolean playbackSpeedIsMutable)
throws ExoPlaybackException {
- this(playbackParameters, playbackParametersAreMutable, true, false, false);
+ this(playbackSpeed, playbackSpeedIsMutable, true, false, false);
}
public MediaClockRenderer(boolean isReady, boolean isEnded, boolean hasReadStreamToEnd)
throws ExoPlaybackException {
- this(PlaybackParameters.DEFAULT, false, isReady, isEnded, hasReadStreamToEnd);
+ this(Player.DEFAULT_PLAYBACK_SPEED, false, isReady, isEnded, hasReadStreamToEnd);
}
- private MediaClockRenderer(PlaybackParameters playbackParameters,
- boolean playbackParametersAreMutable, boolean isReady, boolean isEnded,
+ private MediaClockRenderer(
+ float playbackSpeed,
+ boolean playbackSpeedIsMutable,
+ boolean isReady,
+ boolean isEnded,
boolean hasReadStreamToEnd)
throws ExoPlaybackException {
- this.playbackParameters = playbackParameters;
- this.playbackParametersAreMutable = playbackParametersAreMutable;
+ super(C.TRACK_TYPE_UNKNOWN);
+ this.playbackSpeed = playbackSpeed;
+ this.playbackSpeedIsMutable = playbackSpeedIsMutable;
this.isReady = isReady;
this.isEnded = isEnded;
this.positionUs = TEST_POSITION_US;
@@ -418,15 +416,15 @@
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
- if (playbackParametersAreMutable) {
- this.playbackParameters = playbackParameters;
+ public void setPlaybackSpeed(float playbackSpeed) {
+ if (playbackSpeedIsMutable) {
+ this.playbackSpeed = playbackSpeed;
}
}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return playbackParameters;
+ public float getPlaybackSpeed() {
+ return playbackSpeed;
}
@Override
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
index 8e42309..b4101dc 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
@@ -44,6 +44,7 @@
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
@@ -52,7 +53,6 @@
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
-import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder;
import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet;
import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource;
import com.google.android.exoplayer2.testutil.FakeChunkSource;
@@ -61,13 +61,17 @@
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
+import com.google.android.exoplayer2.testutil.FakeSampleStream;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.FakeTrackSelection;
import com.google.android.exoplayer2.testutil.FakeTrackSelector;
+import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.upstream.Allocation;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
@@ -83,6 +87,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.LooperMode;
@@ -93,6 +98,8 @@
@LooperMode(LooperMode.Mode.PAUSED)
public final class ExoPlayerTest {
+ private static final String TAG = "ExoPlayerTest";
+
/**
* For tests that rely on the player transitioning to the ended state, the duration in
* milliseconds after starting the player before the test will time out. This is to catch cases
@@ -114,15 +121,15 @@
* error.
*/
@Test
- public void testPlayEmptyTimeline() throws Exception {
+ public void playEmptyTimeline() throws Exception {
Timeline timeline = Timeline.EMPTY;
Timeline expectedMaskingTimeline = new MaskingMediaSource.DummyTimeline(/* tag= */ null);
- FakeRenderer renderer = new FakeRenderer();
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_UNKNOWN);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setRenderers(renderer)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertNoPositionDiscontinuities();
@@ -130,23 +137,23 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- assertThat(renderer.formatReadCount).isEqualTo(0);
+ assertThat(renderer.getFormatsRead()).isEmpty();
assertThat(renderer.sampleBufferReadCount).isEqualTo(0);
assertThat(renderer.isEnded).isFalse();
}
/** Tests playback of a source that exposes a single period. */
@Test
- public void testPlaySinglePeriodTimeline() throws Exception {
+ public void playSinglePeriodTimeline() throws Exception {
Object manifest = new Object();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1, manifest);
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setManifest(manifest)
.setRenderers(renderer)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertNoPositionDiscontinuities();
@@ -154,22 +161,23 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
- assertThat(renderer.formatReadCount).isEqualTo(1);
+ testRunner.assertTrackGroupsEqual(
+ new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)));
+ assertThat(renderer.getFormatsRead()).containsExactly(ExoPlayerTestRunner.VIDEO_FORMAT);
assertThat(renderer.sampleBufferReadCount).isEqualTo(1);
assertThat(renderer.isEnded).isTrue();
}
/** Tests playback of a source that exposes three periods. */
@Test
- public void testPlayMultiPeriodTimeline() throws Exception {
+ public void playMultiPeriodTimeline() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setRenderers(renderer)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(
@@ -179,23 +187,27 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- assertThat(renderer.formatReadCount).isEqualTo(3);
+ assertThat(renderer.getFormatsRead())
+ .containsExactly(
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.VIDEO_FORMAT);
assertThat(renderer.sampleBufferReadCount).isEqualTo(3);
assertThat(renderer.isEnded).isTrue();
}
/** Tests playback of periods with very short duration. */
@Test
- public void testPlayShortDurationPeriods() throws Exception {
+ public void playShortDurationPeriods() throws Exception {
// TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US / 100 = 1000 us per period.
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 100, /* id= */ 0));
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setRenderers(renderer)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
Integer[] expectedReasons = new Integer[99];
@@ -205,7 +217,7 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- assertThat(renderer.formatReadCount).isEqualTo(100);
+ assertThat(renderer.getFormatsRead()).hasSize(100);
assertThat(renderer.sampleBufferReadCount).isEqualTo(100);
assertThat(renderer.isEnded).isTrue();
}
@@ -215,7 +227,7 @@
* source.
*/
@Test
- public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
+ public void readAheadToEndDoesNotResetRenderer() throws Exception {
// Use sufficiently short periods to ensure the player attempts to read all at once.
TimelineWindowDefinition windowDefinition0 =
new TimelineWindowDefinition(
@@ -239,9 +251,9 @@
/* isDynamic= */ false,
/* durationUs= */ 100_000);
Timeline timeline = new FakeTimeline(windowDefinition0, windowDefinition1, windowDefinition2);
- final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ final FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeMediaClockRenderer audioRenderer =
- new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) {
+ new FakeMediaClockRenderer(C.TRACK_TYPE_AUDIO) {
@Override
public long getPositionUs() {
@@ -253,11 +265,11 @@
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
+ public void setPlaybackSpeed(float playbackSpeed) {}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return PlaybackParameters.DEFAULT;
+ public float getPlaybackSpeed() {
+ return Player.DEFAULT_PLAYBACK_SPEED;
}
@Override
@@ -266,11 +278,11 @@
}
};
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setRenderers(videoRenderer, audioRenderer)
- .setSupportedFormats(Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT)
- .build(context)
+ .setSupportedFormats(ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(
@@ -286,19 +298,19 @@
}
@Test
- public void testResettingMediaSourcesGivesFreshSourceInfo() throws Exception {
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ public void resettingMediaSourcesGivesFreshSourceInfo() throws Exception {
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
Timeline firstTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true, /* isDynamic= */ false, 1000_000_000));
- MediaSource firstSource = new FakeMediaSource(firstTimeline, Builder.VIDEO_FORMAT);
+ MediaSource firstSource = new FakeMediaSource(firstTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1);
final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondSource =
- new FakeMediaSource(secondTimeline, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(secondTimeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
public synchronized void prepareSourceInternal(
@Nullable TransferListener mediaTransferListener) {
@@ -316,14 +328,14 @@
};
Object thirdSourceManifest = new Object();
Timeline thirdTimeline = new FakeTimeline(/* windowCount= */ 1, thirdSourceManifest);
- MediaSource thirdSource = new FakeMediaSource(thirdTimeline, Builder.VIDEO_FORMAT);
+ MediaSource thirdSource = new FakeMediaSource(thirdTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline. Prepare
// the player again with a source and a new manifest, which will never be exposed. Allow the
// test thread to set a third source, and block the playback thread until the test thread's call
// to setMediaSources() has returned.
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testResettingMediaSourcesGivesFreshSourceInfo")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(
firstTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.setMediaSources(secondSource)
@@ -339,11 +351,11 @@
.executeRunnable(completePreparationCountDownLatch::countDown)
.build();
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstSource)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -360,16 +372,17 @@
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
+ testRunner.assertTrackGroupsEqual(
+ new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)));
assertThat(renderer.isEnded).isTrue();
}
@Test
- public void testRepeatModeChanges() throws Exception {
+ public void repeatModeChanges() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRepeatMode")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
@@ -389,11 +402,11 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2);
@@ -415,18 +428,18 @@
}
@Test
- public void testShuffleModeEnabledChanges() throws Exception {
+ public void shuffleModeEnabledChanges() throws Exception {
Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource[] fakeMediaSources = {
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)
};
ConcatenatingMediaSource mediaSource =
new ConcatenatingMediaSource(false, new FakeShuffleOrder(3), fakeMediaSources);
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testShuffleModeEnabled")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.setRepeatMode(Player.REPEAT_MODE_ALL)
@@ -438,11 +451,11 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 0, 2, 1, 2);
@@ -456,10 +469,12 @@
}
@Test
- public void testAdGroupWithLoadErrorIsSkipped() throws Exception {
+ public void adGroupWithLoadErrorIsSkipped() throws Exception {
AdPlaybackState initialAdPlaybackState =
FakeTimeline.createAdPlaybackState(
- /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 5 * C.MICROS_PER_SECOND);
+ /* adsPerAdGroup= */ 1, /* adGroupTimesUs=... */
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
+ + 5 * C.MICROS_PER_SECOND);
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -479,21 +494,22 @@
/* isDynamic= */ false,
/* durationUs= */ C.MICROS_PER_SECOND,
errorAdPlaybackState));
- final FakeMediaSource fakeMediaSource = new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource fakeMediaSource =
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testAdGroupWithLoadErrorIsSkipped")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
- .executeRunnable(() -> fakeMediaSource.setNewSourceInfo(adErrorTimeline, null))
+ .executeRunnable(() -> fakeMediaSource.setNewSourceInfo(adErrorTimeline))
.waitForTimelineChanged(
adErrorTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(fakeMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
// There is still one discontinuity from content to content for the failed ad insertion.
@@ -501,95 +517,30 @@
}
@Test
- public void testPeriodHoldersReleasedAfterSeekWithRepeatModeAll() throws Exception {
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ public void periodHoldersReleasedAfterSeekWithRepeatModeAll() throws Exception {
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testPeriodHoldersReleased")
+ new ActionSchedule.Builder(TAG)
.setRepeatMode(Player.REPEAT_MODE_ALL)
.waitForPositionDiscontinuity()
.seek(0) // Seek with repeat mode set to Player.REPEAT_MODE_ALL.
.waitForPositionDiscontinuity()
.setRepeatMode(Player.REPEAT_MODE_OFF) // Turn off repeat so that playback can finish.
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(renderer.isEnded).isTrue();
}
@Test
- public void testSeekProcessedCallback() throws Exception {
- Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
- ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekProcessedCallback")
- // Initial seek. Expect immediate seek processed.
- .pause()
- .seek(5)
- .waitForSeekProcessed()
- // Multiple overlapping seeks while the player is still preparing. Expect only one seek
- // processed.
- .seek(2)
- .seek(10)
- // Wait until media source prepared and re-seek to same position. Expect a seek
- // processed while still being in STATE_READY.
- .waitForPlaybackState(Player.STATE_READY)
- .seek(10)
- // Start playback and wait until playback reaches second window.
- .playUntilStartOfWindow(/* windowIndex= */ 1)
- // Seek twice in concession, expecting the first seek to be replaced (and thus except
- // only on seek processed callback).
- .seek(5)
- .seek(60)
- .play()
- .build();
- final List<Integer> playbackStatesWhenSeekProcessed = new ArrayList<>();
- EventListener eventListener =
- new EventListener() {
- private int currentPlaybackState = Player.STATE_IDLE;
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
- currentPlaybackState = playbackState;
- }
-
- @Override
- public void onSeekProcessed() {
- playbackStatesWhenSeekProcessed.add(currentPlaybackState);
- }
- };
- ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
- .setTimeline(timeline)
- .setEventListener(eventListener)
- .setActionSchedule(actionSchedule)
- .build(context)
- .start()
- .blockUntilEnded(TIMEOUT_MS);
- testRunner.assertPositionDiscontinuityReasonsEqual(
- Player.DISCONTINUITY_REASON_SEEK,
- Player.DISCONTINUITY_REASON_SEEK,
- Player.DISCONTINUITY_REASON_SEEK,
- Player.DISCONTINUITY_REASON_SEEK,
- Player.DISCONTINUITY_REASON_PERIOD_TRANSITION,
- Player.DISCONTINUITY_REASON_SEEK,
- Player.DISCONTINUITY_REASON_SEEK);
- assertThat(playbackStatesWhenSeekProcessed)
- .containsExactly(
- Player.STATE_BUFFERING,
- Player.STATE_BUFFERING,
- Player.STATE_READY,
- Player.STATE_BUFFERING)
- .inOrder();
- }
-
- @Test
- public void testIllegalSeekPositionDoesThrow() throws Exception {
+ public void illegalSeekPositionDoesThrow() throws Exception {
final IllegalSeekPositionException[] exception = new IllegalSeekPositionException[1];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testIllegalSeekPositionDoesThrow")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
new PlayerRunnable() {
@@ -604,9 +555,9 @@
})
.waitForPlaybackState(Player.STATE_ENDED)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -614,25 +565,24 @@
}
@Test
- public void testSeekDiscontinuity() throws Exception {
+ public void seekDiscontinuity() throws Exception {
FakeTimeline timeline = new FakeTimeline(1);
- ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekDiscontinuity").seek(10).build();
+ ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG).seek(10).build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
}
@Test
- public void testSeekDiscontinuityWithAdjustment() throws Exception {
+ public void seekDiscontinuityWithAdjustment() throws Exception {
FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
@@ -646,17 +596,17 @@
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekDiscontinuityAdjust")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.seek(10)
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(
@@ -664,10 +614,10 @@
}
@Test
- public void testInternalDiscontinuityAtNewPosition() throws Exception {
+ public void internalDiscontinuityAtNewPosition() throws Exception {
FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
@@ -681,19 +631,19 @@
}
};
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_INTERNAL);
}
@Test
- public void testInternalDiscontinuityAtInitialPosition() throws Exception {
+ public void internalDiscontinuityAtInitialPosition() throws Exception {
FakeTimeline timeline = new FakeTimeline(1);
FakeMediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
@@ -702,14 +652,15 @@
EventDispatcher eventDispatcher,
@Nullable TransferListener transferListener) {
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(trackGroupArray, eventDispatcher);
- mediaPeriod.setDiscontinuityPositionUs(0);
+ mediaPeriod.setDiscontinuityPositionUs(
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US);
return mediaPeriod;
}
};
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
// If the position is unchanged we do not expect the discontinuity to be reported externally.
@@ -717,19 +668,20 @@
}
@Test
- public void testAllActivatedTrackSelectionAreReleasedForSinglePeriod() throws Exception {
+ public void allActivatedTrackSelectionAreReleasedForSinglePeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
- FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
+ new FakeMediaSource(
+ timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
FakeTrackSelector trackSelector = new FakeTrackSelector();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -746,19 +698,20 @@
}
@Test
- public void testAllActivatedTrackSelectionAreReleasedForMultiPeriods() throws Exception {
+ public void allActivatedTrackSelectionAreReleasedForMultiPeriods() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
- FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
+ new FakeMediaSource(
+ timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
FakeTrackSelector trackSelector = new FakeTrackSelector();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -775,28 +728,28 @@
}
@Test
- public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreRemade()
- throws Exception {
+ public void allActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreRemade() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
- FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
+ new FakeMediaSource(
+ timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
final FakeTrackSelector trackSelector = new FakeTrackSelector();
ActionSchedule disableTrackAction =
- new ActionSchedule.Builder("testChangeTrackSelection")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.disableRenderer(0)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.setActionSchedule(disableTrackAction)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -814,28 +767,29 @@
}
@Test
- public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused()
- throws Exception {
+ public void allActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource =
- new FakeMediaSource(timeline, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
- FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
- final FakeTrackSelector trackSelector = new FakeTrackSelector(/* reuse track selection */ true);
+ new FakeMediaSource(
+ timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
+ final FakeTrackSelector trackSelector =
+ new FakeTrackSelector(/* mayReuseTrackSelection= */ true);
ActionSchedule disableTrackAction =
- new ActionSchedule.Builder("testReuseTrackSelection")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.disableRenderer(0)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.setActionSchedule(disableTrackAction)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -854,25 +808,26 @@
}
@Test
- public void testDynamicTimelineChangeReason() throws Exception {
+ public void dynamicTimelineChangeReason() throws Exception {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 100000));
final Timeline timeline2 = new FakeTimeline(new TimelineWindowDefinition(false, false, 20000));
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testDynamicTimelineChangeReason")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
- .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2, null))
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2))
.waitForTimelineChanged(
timeline2, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(dummyTimeline, timeline, timeline2);
@@ -883,7 +838,7 @@
}
@Test
- public void testResetMediaSourcesWithPositionResetAndShufflingUsesFirstPeriod() throws Exception {
+ public void resetMediaSourcesWithPositionResetAndShufflingUsesFirstPeriod() throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -892,17 +847,16 @@
new ConcatenatingMediaSource(
/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT));
ConcatenatingMediaSource secondMediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT));
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testResetMediaSourcesWithPositionResetAndShufflingUsesFirstPeriod")
+ new ActionSchedule.Builder(TAG)
// Wait for first preparation and enable shuffling. Plays period 0.
.pause()
.waitForPlaybackState(Player.STATE_READY)
@@ -914,10 +868,10 @@
.waitForPositionDiscontinuity()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -925,13 +879,14 @@
}
@Test
- public void testSetPlaybackParametersBeforePreparationCompletesSucceeds() throws Exception {
+ public void setPlaybackParametersBeforePreparationCompletesSucceeds() throws Exception {
// Test that no exception is thrown when playback parameters are updated between creating a
// period and preparation of the period completing.
final CountDownLatch createPeriodCalledCountDownLatch = new CountDownLatch(1);
final FakeMediaPeriod[] fakeMediaPeriodHolder = new FakeMediaPeriod[1];
MediaSource mediaSource =
- new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
@@ -947,7 +902,7 @@
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetPlaybackParametersBeforePreparationCompletesSucceeds")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
// Block until createPeriod has been called on the fake media source.
.executeRunnable(
@@ -958,25 +913,89 @@
throw new IllegalStateException(e);
}
})
- // Set playback parameters (while the fake media period is not yet prepared).
- .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f, /* pitch= */ 2f))
+ // Set playback speed (while the fake media period is not yet prepared).
+ .setPlaybackSpeed(2f)
// Complete preparation of the fake media period.
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
}
@Test
- public void testStopDoesNotResetPosition() throws Exception {
+ public void seekBeforePreparationCompletes_seeksToCorrectPosition() throws Exception {
+ CountDownLatch createPeriodCalledCountDownLatch = new CountDownLatch(1);
+ FakeMediaPeriod[] fakeMediaPeriodHolder = new FakeMediaPeriod[1];
+ Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
+ FakeMediaSource mediaSource =
+ new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) {
+ @Override
+ protected FakeMediaPeriod createFakeMediaPeriod(
+ MediaPeriodId id,
+ TrackGroupArray trackGroupArray,
+ Allocator allocator,
+ EventDispatcher eventDispatcher,
+ @Nullable TransferListener transferListener) {
+ // Defer completing preparation of the period until seek has been sent.
+ fakeMediaPeriodHolder[0] =
+ new FakeMediaPeriod(trackGroupArray, eventDispatcher, /* deferOnPrepared= */ true);
+ createPeriodCalledCountDownLatch.countDown();
+ return fakeMediaPeriodHolder[0];
+ }
+ };
+ AtomicLong positionWhenReady = new AtomicLong();
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .pause()
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ // Ensure we use the MaskingMediaPeriod by delaying the initial timeline update.
+ .delay(1)
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline))
+ .waitForTimelineChanged()
+ // Block until createPeriod has been called on the fake media source.
+ .executeRunnable(
+ () -> {
+ try {
+ createPeriodCalledCountDownLatch.await();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ })
+ // Seek before preparation completes.
+ .seek(5000)
+ // Complete preparation of the fake media period.
+ .executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
+ .waitForPlaybackState(Player.STATE_READY)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ positionWhenReady.set(player.getCurrentPosition());
+ }
+ })
+ .play()
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000)
+ .setMediaSources(mediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(positionWhenReady.get()).isAtLeast(5000);
+ }
+
+ @Test
+ public void stopDoesNotResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopDoesNotResetPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
@@ -990,10 +1009,10 @@
})
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1006,11 +1025,11 @@
}
@Test
- public void testStopWithoutResetDoesNotResetPosition() throws Exception {
+ public void stopWithoutResetDoesNotResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopWithoutResetDoesNotReset")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
@@ -1024,10 +1043,10 @@
})
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1040,11 +1059,11 @@
}
@Test
- public void testStopWithResetDoesResetPosition() throws Exception {
+ public void stopWithResetDoesResetPosition() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopWithResetDoesReset")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
@@ -1058,10 +1077,10 @@
})
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1075,19 +1094,20 @@
}
@Test
- public void testStopWithoutResetReleasesMediaSource() throws Exception {
+ public void stopWithoutResetReleasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopReleasesMediaSource")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ false)
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
mediaSource.assertReleased();
@@ -1095,19 +1115,20 @@
}
@Test
- public void testStopWithResetReleasesMediaSource() throws Exception {
+ public void stopWithResetReleasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopReleasesMediaSource")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true)
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
mediaSource.assertReleased();
@@ -1115,14 +1136,15 @@
}
@Test
- public void testSettingNewStartPositionPossibleAfterStopWithReset() throws Exception {
+ public void settingNewStartPositionPossibleAfterStopWithReset() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2);
- MediaSource secondSource = new FakeMediaSource(secondTimeline, Builder.VIDEO_FORMAT);
+ MediaSource secondSource =
+ new FakeMediaSource(secondTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
AtomicInteger windowIndexAfterStop = new AtomicInteger();
AtomicLong positionAfterStop = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSettingNewStartPositionPossibleAfterStopWithReset")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true)
.waitForPlaybackState(Player.STATE_IDLE)
@@ -1140,15 +1162,14 @@
.waitForPlaybackState(Player.STATE_READY)
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.setExpectedPlayerEndedCount(2)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_BUFFERING,
Player.STATE_READY,
Player.STATE_IDLE,
@@ -1173,7 +1194,7 @@
}
@Test
- public void testResetPlaylistWithPreviousPosition() throws Exception {
+ public void resetPlaylistWithPreviousPosition() throws Exception {
Object firstWindowId = new Object();
Timeline timeline =
new FakeTimeline(
@@ -1189,7 +1210,7 @@
MediaSource secondSource = new FakeMediaSource(secondTimeline);
AtomicLong positionAfterReprepare = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testResetPlaylistWithPreviousPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000)
@@ -1206,10 +1227,10 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1225,7 +1246,7 @@
}
@Test
- public void testResetPlaylistStartsFromDefaultPosition() throws Exception {
+ public void resetPlaylistStartsFromDefaultPosition() throws Exception {
Object firstWindowId = new Object();
Timeline timeline =
new FakeTimeline(
@@ -1241,7 +1262,7 @@
MediaSource secondSource = new FakeMediaSource(secondTimeline);
AtomicLong positionAfterReprepare = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testResetPlaylistStartsFromDefaultPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000)
@@ -1258,10 +1279,10 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1277,7 +1298,7 @@
}
@Test
- public void testResetPlaylistWithoutResettingPositionStartsFromOldPosition() throws Exception {
+ public void resetPlaylistWithoutResettingPositionStartsFromOldPosition() throws Exception {
Object firstWindowId = new Object();
Timeline timeline =
new FakeTimeline(
@@ -1293,7 +1314,7 @@
MediaSource secondSource = new FakeMediaSource(secondTimeline);
AtomicLong positionAfterReprepare = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testResetPlaylistWithoutResettingPositionStartsFromOldPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000)
@@ -1310,10 +1331,10 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1329,20 +1350,19 @@
}
@Test
- public void testStopDuringPreparationOverwritesPreparation() throws Exception {
+ public void stopDuringPreparationOverwritesPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopOverwritesPrepare")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
- .seek(0)
.stop(true)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1350,27 +1370,25 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
- testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
}
@Test
- public void testStopAndSeekAfterStopDoesNotResetTimeline() throws Exception {
- // Combining additional stop and seek after initial stop in one test to get the seek processed
- // callback which ensures that all operations have been processed by the player.
+ public void stopAndSeekAfterStopDoesNotResetTimeline() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testStopTwice")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(false)
.stop(false)
- .seek(0)
- .waitForSeekProcessed()
+ // Wait until the player fully processed the second stop to see that no further
+ // callbacks are triggered.
+ .waitForPendingPlayerCommands()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1378,14 +1396,13 @@
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
}
@Test
- public void testReprepareAfterPlaybackError() throws Exception {
+ public void reprepareAfterPlaybackError() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testReprepareAfterPlaybackError")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
.waitForPlaybackState(Player.STATE_IDLE)
@@ -1393,10 +1410,10 @@
.waitForPlaybackState(Player.STATE_BUFFERING)
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
@@ -1410,17 +1427,17 @@
}
@Test
- public void testSeekAndReprepareAfterPlaybackError() throws Exception {
+ public void seekAndReprepareAfterPlaybackError() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[2];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testReprepareAfterPlaybackError")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
.waitForPlaybackState(Player.STATE_IDLE)
.seek(/* positionMs= */ 50)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -1440,16 +1457,18 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context);
- try {
- testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
- fail();
- } catch (ExoPlaybackException e) {
- // Expected exception.
- }
+ .build();
+
+ assertThrows(
+ ExoPlaybackException.class,
+ () ->
+ testRunner
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS));
testRunner.assertTimelinesSame(dummyTimeline, timeline);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@@ -1466,7 +1485,7 @@
FakeMediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2));
AtomicInteger windowIndexAfterUpdate = new AtomicInteger();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testInvalidSeekPositionSourceInfoRefreshUsesCorrectFirstPeriod")
+ new ActionSchedule.Builder(TAG)
.setShuffleOrder(new FakeShuffleOrder(/* length= */ 0))
.setShuffleModeEnabled(true)
.waitForPlaybackState(Player.STATE_BUFFERING)
@@ -1482,10 +1501,10 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1494,7 +1513,7 @@
}
@Test
- public void testRestartAfterEmptyTimelineWithShuffleModeEnabledUsesCorrectFirstPeriod()
+ public void restartAfterEmptyTimelineWithShuffleModeEnabledUsesCorrectFirstPeriod()
throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource mediaSource = new FakeMediaSource(timeline);
@@ -1502,8 +1521,7 @@
new ConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0));
AtomicInteger windowIndexAfterAddingSources = new AtomicInteger();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testRestartAfterEmptyTimelineWithShuffleModeEnabledUsesCorrectFirstPeriod")
+ new ActionSchedule.Builder(TAG)
.setShuffleModeEnabled(true)
// Preparing with an empty media source will transition to ended state.
.waitForPlaybackState(Player.STATE_ENDED)
@@ -1522,10 +1540,10 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1533,13 +1551,13 @@
}
@Test
- public void testPlaybackErrorAndReprepareDoesNotResetPosition() throws Exception {
+ public void playbackErrorAndReprepareDoesNotResetPosition() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
final long[] positionHolder = new long[3];
final int[] windowIndexHolder = new int[3];
final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testPlaybackErrorDoesNotResetPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500)
@@ -1577,10 +1595,10 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
@@ -1596,13 +1614,13 @@
}
@Test
- public void testSeekAfterPlaybackError() throws Exception {
+ public void seekAfterPlaybackError() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
final long[] positionHolder = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final int[] windowIndexHolder = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
final FakeMediaSource firstMediaSource = new FakeMediaSource(timeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekAfterPlaybackError")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500)
@@ -1618,7 +1636,7 @@
}
})
.seek(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -1641,16 +1659,18 @@
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context);
- try {
- testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
- fail();
- } catch (ExoPlaybackException e) {
- // Expected exception.
- }
+ .build();
+
+ assertThrows(
+ ExoPlaybackException.class,
+ () ->
+ testRunner
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS));
assertThat(positionHolder[0]).isAtLeast(500L);
assertThat(positionHolder[1]).isEqualTo(0L);
assertThat(positionHolder[2]).isEqualTo(0L);
@@ -1664,7 +1684,7 @@
throws Exception {
FakeMediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1));
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("playbackErrorWithResetKeepsWindowSequenceNumber")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
@@ -1686,11 +1706,11 @@
}
};
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
.setAnalyticsListener(listener)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
@@ -1701,11 +1721,11 @@
}
@Test
- public void testPlaybackErrorTwiceStillKeepsTimeline() throws Exception {
+ public void playbackErrorTwiceStillKeepsTimeline() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource2 = new FakeMediaSource(timeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testPlaybackErrorDoesNotResetPosition")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
@@ -1718,10 +1738,10 @@
.waitForPlaybackState(Player.STATE_IDLE)
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
@@ -1737,63 +1757,63 @@
}
@Test
- public void testSendMessagesDuringPreparation() throws Exception {
+ public void sendMessagesDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
}
@Test
- public void testSendMessagesAfterPreparation() throws Exception {
+ public void sendMessagesAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.sendMessage(target, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
}
@Test
- public void testMultipleSendMessages() throws Exception {
+ public void multipleSendMessages() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target50 = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget target80 = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target80, /* positionMs= */ 80)
.sendMessage(target50, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target50.positionMs).isAtLeast(50L);
@@ -1802,11 +1822,11 @@
}
@Test
- public void testSendMessagesFromStartPositionOnlyOnce() throws Exception {
+ public void sendMessagesFromStartPositionOnlyOnce() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
AtomicInteger counter = new AtomicInteger();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessagesFromStartPositionOnlyOnce")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged()
.pause()
.sendMessage(
@@ -1820,10 +1840,10 @@
.delay(/* delayMs= */ 2000)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1831,22 +1851,22 @@
}
@Test
- public void testMultipleSendMessagesAtSameTime() throws Exception {
+ public void multipleSendMessagesAtSameTime() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target1 = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget target2 = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target1, /* positionMs= */ 50)
.sendMessage(target2, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target1.positionMs).isAtLeast(50L);
@@ -1854,127 +1874,154 @@
}
@Test
- public void testSendMessagesMultiPeriodResolution() throws Exception {
+ public void sendMessagesMultiPeriodResolution() throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 10, /* id= */ 0));
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
}
@Test
- public void testSendMessagesAtStartAndEndOfPeriod() throws Exception {
+ public void sendMessagesAtStartAndEndOfPeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
PositionGrabbingMessageTarget targetStartFirstPeriod = new PositionGrabbingMessageTarget();
- PositionGrabbingMessageTarget targetEndMiddlePeriod = new PositionGrabbingMessageTarget();
+ PositionGrabbingMessageTarget targetEndMiddlePeriodResolved =
+ new PositionGrabbingMessageTarget();
+ PositionGrabbingMessageTarget targetEndMiddlePeriodUnresolved =
+ new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetStartMiddlePeriod = new PositionGrabbingMessageTarget();
- PositionGrabbingMessageTarget targetEndLastPeriod = new PositionGrabbingMessageTarget();
+ PositionGrabbingMessageTarget targetEndLastPeriodResolved = new PositionGrabbingMessageTarget();
+ PositionGrabbingMessageTarget targetEndLastPeriodUnresolved =
+ new PositionGrabbingMessageTarget();
long duration1Ms = timeline.getWindow(0, new Window()).getDurationMs();
long duration2Ms = timeline.getWindow(1, new Window()).getDurationMs();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessagesAtStartAndEndOfPeriod")
+ new ActionSchedule.Builder(TAG)
.sendMessage(targetStartFirstPeriod, /* windowIndex= */ 0, /* positionMs= */ 0)
- .sendMessage(targetEndMiddlePeriod, /* windowIndex= */ 0, /* positionMs= */ duration1Ms)
+ .sendMessage(
+ targetEndMiddlePeriodResolved,
+ /* windowIndex= */ 0,
+ /* positionMs= */ duration1Ms - 1)
+ .sendMessage(
+ targetEndMiddlePeriodUnresolved,
+ /* windowIndex= */ 0,
+ /* positionMs= */ C.TIME_END_OF_SOURCE)
.sendMessage(targetStartMiddlePeriod, /* windowIndex= */ 1, /* positionMs= */ 0)
- .sendMessage(targetEndLastPeriod, /* windowIndex= */ 1, /* positionMs= */ duration2Ms)
- .waitForMessage(targetEndLastPeriod)
+ .sendMessage(
+ targetEndLastPeriodResolved,
+ /* windowIndex= */ 1,
+ /* positionMs= */ duration2Ms - 1)
+ .sendMessage(
+ targetEndLastPeriodUnresolved,
+ /* windowIndex= */ 1,
+ /* positionMs= */ C.TIME_END_OF_SOURCE)
+ .waitForMessage(targetEndLastPeriodUnresolved)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(targetStartFirstPeriod.windowIndex).isEqualTo(0);
assertThat(targetStartFirstPeriod.positionMs).isAtLeast(0L);
- assertThat(targetEndMiddlePeriod.windowIndex).isEqualTo(0);
- assertThat(targetEndMiddlePeriod.positionMs).isAtLeast(duration1Ms);
+ assertThat(targetEndMiddlePeriodResolved.windowIndex).isEqualTo(0);
+ assertThat(targetEndMiddlePeriodResolved.positionMs).isAtLeast(duration1Ms - 1);
+ assertThat(targetEndMiddlePeriodUnresolved.windowIndex).isEqualTo(0);
+ assertThat(targetEndMiddlePeriodUnresolved.positionMs).isAtLeast(duration1Ms - 1);
+ assertThat(targetEndMiddlePeriodResolved.positionMs)
+ .isEqualTo(targetEndMiddlePeriodUnresolved.positionMs);
assertThat(targetStartMiddlePeriod.windowIndex).isEqualTo(1);
assertThat(targetStartMiddlePeriod.positionMs).isAtLeast(0L);
- assertThat(targetEndLastPeriod.windowIndex).isEqualTo(1);
- assertThat(targetEndLastPeriod.positionMs).isAtLeast(duration2Ms);
+ assertThat(targetEndLastPeriodResolved.windowIndex).isEqualTo(1);
+ assertThat(targetEndLastPeriodResolved.positionMs).isAtLeast(duration2Ms - 1);
+ assertThat(targetEndLastPeriodUnresolved.windowIndex).isEqualTo(1);
+ assertThat(targetEndLastPeriodUnresolved.positionMs).isAtLeast(duration2Ms - 1);
+ assertThat(targetEndLastPeriodResolved.positionMs)
+ .isEqualTo(targetEndLastPeriodUnresolved.positionMs);
}
@Test
- public void testSendMessagesSeekOnDeliveryTimeDuringPreparation() throws Exception {
+ public void sendMessagesSeekOnDeliveryTimeDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
.seek(/* positionMs= */ 50)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
}
@Test
- public void testSendMessagesSeekOnDeliveryTimeAfterPreparation() throws Exception {
+ public void sendMessagesSeekOnDeliveryTimeAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.seek(/* positionMs= */ 50)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
}
@Test
- public void testSendMessagesSeekAfterDeliveryTimeDuringPreparation() throws Exception {
+ public void sendMessagesSeekAfterDeliveryTimeDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
.seek(/* positionMs= */ 51)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
}
@Test
- public void testSendMessagesSeekAfterDeliveryTimeAfterPreparation() throws Exception {
+ public void sendMessagesSeekAfterDeliveryTimeAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.sendMessage(target, /* positionMs= */ 50)
.waitForTimelineChanged(
@@ -1982,21 +2029,21 @@
.seek(/* positionMs= */ 51)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
}
@Test
- public void testSendMessagesRepeatDoesNotRepost() throws Exception {
+ public void sendMessagesRepeatDoesNotRepost() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(target, /* positionMs= */ 50)
@@ -2005,10 +2052,10 @@
.waitForPositionDiscontinuity()
.setRepeatMode(Player.REPEAT_MODE_OFF)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.messageCount).isEqualTo(1);
@@ -2016,11 +2063,11 @@
}
@Test
- public void testSendMessagesRepeatWithoutDeletingDoesRepost() throws Exception {
+ public void sendMessagesRepeatWithoutDeletingDoesRepost() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.sendMessage(
@@ -2034,10 +2081,10 @@
.setRepeatMode(Player.REPEAT_MODE_OFF)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.messageCount).isEqualTo(2);
@@ -2045,30 +2092,31 @@
}
@Test
- public void testSendMessagesMoveCurrentWindowIndex() throws Exception {
+ public void sendMessagesMoveCurrentWindowIndex() throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0));
final Timeline secondTimeline =
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0));
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.sendMessage(target, /* positionMs= */ 50)
- .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline, null))
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline))
.waitForTimelineChanged(
secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
@@ -2076,20 +2124,20 @@
}
@Test
- public void testSendMessagesMultiWindowDuringPreparation() throws Exception {
+ public void sendMessagesMultiWindowDuringPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.windowIndex).isEqualTo(2);
@@ -2097,21 +2145,21 @@
}
@Test
- public void testSendMessagesMultiWindowAfterPreparation() throws Exception {
+ public void sendMessagesMultiWindowAfterPreparation() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 3);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.windowIndex).isEqualTo(2);
@@ -2119,7 +2167,7 @@
}
@Test
- public void testSendMessagesMoveWindowIndex() throws Exception {
+ public void sendMessagesMoveWindowIndex() throws Exception {
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0),
@@ -2128,24 +2176,25 @@
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0));
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForTimelineChanged(
timeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.sendMessage(target, /* windowIndex = */ 1, /* positionMs= */ 50)
- .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline, null))
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(secondTimeline))
.waitForTimelineChanged(
secondTimeline, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.seek(/* windowIndex= */ 0, /* positionMs= */ 0)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target.positionMs).isAtLeast(50L);
@@ -2153,12 +2202,12 @@
}
@Test
- public void testSendMessagesNonLinearPeriodOrder() throws Exception {
+ public void sendMessagesNonLinearPeriodOrder() throws Exception {
Timeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource[] fakeMediaSources = {
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT)
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)
};
ConcatenatingMediaSource mediaSource =
new ConcatenatingMediaSource(false, new FakeShuffleOrder(3), fakeMediaSources);
@@ -2166,7 +2215,7 @@
PositionGrabbingMessageTarget target2 = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget target3 = new PositionGrabbingMessageTarget();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSendMessages")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.sendMessage(target1, /* windowIndex = */ 0, /* positionMs= */ 50)
@@ -2176,10 +2225,10 @@
.seek(/* windowIndex= */ 2, /* positionMs= */ 0)
.play()
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(target1.windowIndex).isEqualTo(0);
@@ -2188,12 +2237,12 @@
}
@Test
- public void testCancelMessageBeforeDelivery() throws Exception {
+ public void cancelMessageBeforeDelivery() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
final AtomicReference<PlayerMessage> message = new AtomicReference<>();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testCancelMessage")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
@@ -2209,10 +2258,10 @@
.executeRunnable(() -> message.get().cancel())
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(message.get().isCanceled()).isTrue();
@@ -2220,12 +2269,12 @@
}
@Test
- public void testCancelRepeatedMessageAfterDelivery() throws Exception {
+ public void cancelRepeatedMessageAfterDelivery() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
- final PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget();
+ final CountingMessageTarget target = new CountingMessageTarget();
final AtomicReference<PlayerMessage> message = new AtomicReference<>();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testCancelMessage")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
@@ -2247,10 +2296,10 @@
.executeRunnable(() -> message.get().cancel())
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(message.get().isCanceled()).isTrue();
@@ -2258,22 +2307,21 @@
}
@Test
- public void testSetAndSwitchSurface() throws Exception {
+ public void setAndSwitchSurface() throws Exception {
final List<Integer> rendererMessages = new ArrayList<>();
Renderer videoRenderer =
- new FakeRenderer(Builder.VIDEO_FORMAT) {
+ new FakeRenderer(C.TRACK_TYPE_VIDEO) {
@Override
public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException {
super.handleMessage(what, object);
rendererMessages.add(what);
}
};
- ActionSchedule actionSchedule =
- addSurfaceSwitch(new ActionSchedule.Builder("testSetAndSwitchSurface")).build();
- new ExoPlayerTestRunner.Builder()
+ ActionSchedule actionSchedule = addSurfaceSwitch(new ActionSchedule.Builder(TAG)).build();
+ new ExoPlayerTestRunner.Builder(context)
.setRenderers(videoRenderer)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -2281,22 +2329,21 @@
}
@Test
- public void testSwitchSurfaceOnEndedState() throws Exception {
+ public void switchSurfaceOnEndedState() throws Exception {
ActionSchedule.Builder scheduleBuilder =
- new ActionSchedule.Builder("testSwitchSurfaceOnEndedState")
- .waitForPlaybackState(Player.STATE_ENDED);
+ new ActionSchedule.Builder(TAG).waitForPlaybackState(Player.STATE_ENDED);
ActionSchedule waitForEndedAndSwitchSchedule = addSurfaceSwitch(scheduleBuilder).build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(Timeline.EMPTY)
.setActionSchedule(waitForEndedAndSwitchSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
}
@Test
- public void testTimelineUpdateDropsPrebufferedPeriods() throws Exception {
+ public void timelineUpdateDropsPrebufferedPeriods() throws Exception {
Timeline timeline1 =
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1),
@@ -2305,25 +2352,26 @@
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3));
- final FakeMediaSource mediaSource = new FakeMediaSource(timeline1, Builder.VIDEO_FORMAT);
+ final FakeMediaSource mediaSource =
+ new FakeMediaSource(timeline1, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testTimelineUpdateDropsPeriods")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
// Ensure next period is pre-buffered by playing until end of first period.
.playUntilPosition(
/* windowIndex= */ 0,
/* positionMs= */ C.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US))
- .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2, /* newManifest= */ null))
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline2))
.waitForTimelineChanged(
timeline2, /* expectedReason */ Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelineChangeReasonsEqual(
@@ -2346,7 +2394,7 @@
}
@Test
- public void testRepeatedSeeksToUnpreparedPeriodInSameWindowKeepsWindowSequenceNumber()
+ public void repeatedSeeksToUnpreparedPeriodInSameWindowKeepsWindowSequenceNumber()
throws Exception {
Timeline timeline =
new FakeTimeline(
@@ -2358,22 +2406,23 @@
/* durationUs= */ 10 * C.MICROS_PER_SECOND));
FakeMediaSource mediaSource = new FakeMediaSource(timeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekToUnpreparedPeriod")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.seek(/* windowIndex= */ 0, /* positionMs= */ 9999)
- .waitForSeekProcessed()
+ // Wait after each seek until the internal player has updated its state.
+ .waitForPendingPlayerCommands()
.seek(/* windowIndex= */ 0, /* positionMs= */ 1)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.seek(/* windowIndex= */ 0, /* positionMs= */ 9999)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.play()
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2391,7 +2440,7 @@
}
@Test
- public void testInvalidSeekFallsBackToSubsequentPeriodOfTheRemovedPeriod() throws Exception {
+ public void invalidSeekFallsBackToSubsequentPeriodOfTheRemovedPeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
CountDownLatch sourceReleasedCountDownLatch = new CountDownLatch(/* count= */ 1);
MediaSource mediaSourceToRemove =
@@ -2407,7 +2456,7 @@
final int[] windowCount = {C.INDEX_UNSET};
final long[] position = {C.TIME_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testInvalidSeekFallsBackToSubsequentPeriodOfTheRemovedPeriod")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
@@ -2429,7 +2478,7 @@
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1000L);
}
})
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2440,10 +2489,10 @@
})
.play()
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -2455,7 +2504,7 @@
}
@Test
- public void testRecursivePlayerChangesReportConsistentValuesForAllListeners() throws Exception {
+ public void recursivePlayerChangesReportConsistentValuesForAllListeners() throws Exception {
// We add two listeners to the player. The first stops the player as soon as it's ready and both
// record the state change events they receive.
final AtomicReference<Player> playerReference = new AtomicReference<>();
@@ -2464,7 +2513,7 @@
final EventListener eventListener1 =
new EventListener() {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
eventListener1States.add(playbackState);
if (playbackState == Player.STATE_READY) {
playerReference.get().stop(/* reset= */ true);
@@ -2474,12 +2523,12 @@
final EventListener eventListener2 =
new EventListener() {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
eventListener2States.add(playbackState);
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRecursivePlayerChanges")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2490,9 +2539,9 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2505,32 +2554,42 @@
}
@Test
- public void testRecursivePlayerChangesAreReportedInCorrectOrder() throws Exception {
+ public void recursivePlayerChangesAreReportedInCorrectOrder() throws Exception {
// The listener stops the player as soon as it's ready (which should report a timeline and state
// change) and sets playWhenReady to false when the timeline callback is received.
final AtomicReference<Player> playerReference = new AtomicReference<>();
final List<Boolean> eventListenerPlayWhenReady = new ArrayList<>();
final List<Integer> eventListenerStates = new ArrayList<>();
+ List<Integer> sequence = new ArrayList<>();
final EventListener eventListener =
new EventListener() {
+
@Override
- public void onTimelineChanged(Timeline timeline, int reason) {
- if (timeline.isEmpty()) {
- playerReference.get().pause();
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
+ eventListenerStates.add(playbackState);
+ if (playbackState == Player.STATE_READY) {
+ playerReference.get().stop(/* reset= */ true);
+ sequence.add(0);
}
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
- eventListenerPlayWhenReady.add(playWhenReady);
- eventListenerStates.add(playbackState);
- if (playbackState == Player.STATE_READY) {
- playerReference.get().stop(/* reset= */ true);
+ public void onTimelineChanged(Timeline timeline, int reason) {
+ if (timeline.isEmpty()) {
+ playerReference.get().pause();
+ sequence.add(1);
}
}
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ eventListenerPlayWhenReady.add(playWhenReady);
+ sequence.add(2);
+ }
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRecursivePlayerChanges")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2540,21 +2599,21 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(eventListenerStates)
- .containsExactly(
- Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE, Player.STATE_IDLE)
+ .containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE)
.inOrder();
- assertThat(eventListenerPlayWhenReady).containsExactly(true, true, true, false).inOrder();
+ assertThat(eventListenerPlayWhenReady).containsExactly(false).inOrder();
+ assertThat(sequence).containsExactly(0, 1, 2).inOrder();
}
@Test
- public void testRecursiveTimelineChangeInStopAreReportedInCorrectOrder() throws Exception {
+ public void recursiveTimelineChangeInStopAreReportedInCorrectOrder() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 2);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 3);
final AtomicReference<ExoPlayer> playerReference = new AtomicReference<>();
@@ -2562,14 +2621,14 @@
final EventListener eventListener =
new EventListener() {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, int state) {
+ public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_IDLE) {
playerReference.get().setMediaSource(secondMediaSource);
}
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRecursiveTimelineChangeInStopAreReportedInCorrectOrder")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2586,10 +2645,10 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.setTimeline(firstTimeline)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -2608,7 +2667,7 @@
}
@Test
- public void testClippedLoopedPeriodsArePlayedFully() throws Exception {
+ public void clippedLoopedPeriodsArePlayedFully() throws Exception {
long startPositionUs = 300_000;
long expectedDurationUs = 700_000;
MediaSource mediaSource =
@@ -2624,7 +2683,7 @@
EventListener eventListener =
new EventListener() {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_READY && clockAtStartMs.get() == C.TIME_UNSET) {
clockAtStartMs.set(clock.elapsedRealtime());
}
@@ -2639,7 +2698,7 @@
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testClippedLoopedPeriodsArePlayedFully")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2657,11 +2716,11 @@
.setRepeatMode(Player.REPEAT_MODE_OFF)
.play()
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setClock(clock)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2671,7 +2730,7 @@
}
@Test
- public void testUpdateTrackSelectorThenSeekToUnpreparedPeriod_returnsEmptyTrackGroups()
+ public void updateTrackSelectorThenSeekToUnpreparedPeriod_returnsEmptyTrackGroups()
throws Exception {
// Use unset duration to prevent pre-loading of the second window.
Timeline timelineUnsetDuration =
@@ -2681,20 +2740,21 @@
Timeline timelineSetDuration = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource =
new ConcatenatingMediaSource(
- new FakeMediaSource(timelineUnsetDuration, Builder.VIDEO_FORMAT),
- new FakeMediaSource(timelineSetDuration, Builder.AUDIO_FORMAT));
+ new FakeMediaSource(timelineUnsetDuration, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(timelineSetDuration, ExoPlayerTestRunner.AUDIO_FORMAT));
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testUpdateTrackSelectorThenSeekToUnpreparedPeriod")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.seek(/* windowIndex= */ 1, /* positionMs= */ 0)
+ .waitForPendingPlayerCommands()
.play()
.build();
List<TrackGroupArray> trackGroupsList = new ArrayList<>();
List<TrackSelectionArray> trackSelectionsList = new ArrayList<>();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
- .setSupportedFormats(Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT)
+ .setSupportedFormats(ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT)
.setActionSchedule(actionSchedule)
.setEventListener(
new EventListener() {
@@ -2705,7 +2765,7 @@
trackSelectionsList.add(trackSelections);
}
})
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(trackGroupsList).hasSize(3);
@@ -2713,14 +2773,16 @@
// Then the seek to an unprepared period will result in empty track groups and selections being
// returned.
// Then the track groups of the 2nd period are reported.
- assertThat(trackGroupsList.get(0).get(0).getFormat(0)).isEqualTo(Builder.VIDEO_FORMAT);
+ assertThat(trackGroupsList.get(0).get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.VIDEO_FORMAT);
assertThat(trackGroupsList.get(1)).isEqualTo(TrackGroupArray.EMPTY);
assertThat(trackSelectionsList.get(1).get(0)).isNull();
- assertThat(trackGroupsList.get(2).get(0).getFormat(0)).isEqualTo(Builder.AUDIO_FORMAT);
+ assertThat(trackGroupsList.get(2).get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
}
@Test
- public void testSecondMediaSourceInPlaylistOnlyThrowsWhenPreviousPeriodIsFullyRead()
+ public void secondMediaSourceInPlaylistOnlyThrowsWhenPreviousPeriodIsFullyRead()
throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
@@ -2728,9 +2790,10 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 10 * C.MICROS_PER_SECOND));
- MediaSource workingMediaSource = new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT);
+ MediaSource workingMediaSource =
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
MediaSource failingMediaSource =
- new FakeMediaSource(/* timeline= */ null, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
@@ -2738,12 +2801,12 @@
};
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(workingMediaSource, failingMediaSource);
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setRenderers(renderer)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilEnded(TIMEOUT_MS);
fail();
@@ -2764,9 +2827,10 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 10 * C.MICROS_PER_SECOND));
- MediaSource workingMediaSource = new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT);
+ MediaSource workingMediaSource =
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
MediaSource failingMediaSource =
- new FakeMediaSource(/* timeline= */ null, Builder.VIDEO_FORMAT) {
+ new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
@@ -2775,19 +2839,19 @@
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(workingMediaSource);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testFailingSecondMediaSourceInPlaylistOnlyThrowsLater")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(() -> concatenatingMediaSource.addMediaSource(failingMediaSource))
.play()
.build();
- FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setActionSchedule(actionSchedule)
.setRenderers(renderer)
- .build(context);
+ .build();
try {
testRunner.start().blockUntilEnded(TIMEOUT_MS);
fail();
@@ -2807,7 +2871,7 @@
MediaSource mediaSource = new FakeMediaSource(timeline);
ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(mediaSource);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("removingLoopingLastPeriodFromPlaylistDoesNotThrow")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
// Play almost to end to ensure the current period is fully buffered.
@@ -2817,10 +2881,10 @@
// Remove the media source.
.executeRunnable(concatenatingMediaSource::clear)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
}
@@ -2838,12 +2902,14 @@
MediaSource concatenatedMediaSource = new ConcatenatingMediaSource(clippedMediaSource);
AtomicLong positionWhenReady = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("seekToUnpreparedWindowWithNonZeroOffsetInConcatenation")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
+ // Seek while unprepared and wait until the player handled all updates.
.seek(/* positionMs= */ 10)
- .waitForSeekProcessed()
- .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null))
+ .waitForPendingPlayerCommands()
+ // Finish preparation.
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline))
.waitForTimelineChanged()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
@@ -2855,10 +2921,10 @@
})
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatedMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2882,13 +2948,12 @@
AtomicInteger periodIndexWhenReady = new AtomicInteger();
AtomicLong positionWhenReady = new AtomicLong();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("seekToUnpreparedWindowWithMultiplePeriodsInConcatenation")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
// Seek 10ms into the second period.
.seek(/* positionMs= */ periodDurationMs + 10)
- .waitForSeekProcessed()
- .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null))
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline))
.waitForTimelineChanged()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
@@ -2901,10 +2966,10 @@
})
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatedMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2940,7 +3005,7 @@
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("periodTransitionReportsCorrectBufferedPosition")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -2955,10 +3020,10 @@
.waitForIsLoading(/* targetIsLoading= */ false)
.play()
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -2978,7 +3043,7 @@
/* isDynamic= */ false,
/* durationUs= */ 10_000_000,
adPlaybackState));
- final FakeMediaSource fakeMediaSource = new FakeMediaSource(fakeTimeline);
+ FakeMediaSource fakeMediaSource = new FakeMediaSource(/* timeline= */ null);
AtomicReference<Player> playerReference = new AtomicReference<>();
AtomicLong contentStartPositionMs = new AtomicLong(C.TIME_UNSET);
EventListener eventListener =
@@ -2991,7 +3056,7 @@
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("contentWithInitialSeekAfterPrerollAd")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -3001,11 +3066,65 @@
}
})
.seek(/* positionMs= */ 5_000)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ .executeRunnable(() -> fakeMediaSource.setNewSourceInfo(fakeTimeline))
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(fakeMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(contentStartPositionMs.get()).isAtLeast(5_000L);
+ }
+
+ @Test
+ public void contentWithoutInitialSeekStartsAtDefaultPositionAfterPrerollAd() throws Exception {
+ AdPlaybackState adPlaybackState =
+ FakeTimeline.createAdPlaybackState(/* adsPerAdGroup= */ 3, /* adGroupTimesUs...= */ 0);
+ Timeline fakeTimeline =
+ new FakeTimeline(
+ new TimelineWindowDefinition(
+ /* periodCount= */ 1,
+ /* id= */ 0,
+ /* isSeekable= */ true,
+ /* isDynamic= */ false,
+ /* isLive= */ false,
+ /* isPlaceholder= */ false,
+ /* durationUs= */ 10_000_000,
+ /* defaultPositionUs= */ 5_000_000,
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
+ adPlaybackState));
+ FakeMediaSource fakeMediaSource = new FakeMediaSource(/* timeline= */ null);
+ AtomicReference<Player> playerReference = new AtomicReference<>();
+ AtomicLong contentStartPositionMs = new AtomicLong(C.TIME_UNSET);
+ EventListener eventListener =
+ new EventListener() {
+ @Override
+ public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
+ if (reason == Player.DISCONTINUITY_REASON_AD_INSERTION) {
+ contentStartPositionMs.set(playerReference.get().getContentPosition());
+ }
+ }
+ };
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ playerReference.set(player);
+ player.addListener(eventListener);
+ }
+ })
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ .executeRunnable(() -> fakeMediaSource.setNewSourceInfo(fakeTimeline))
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(fakeMediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -3016,35 +3135,30 @@
public void setPlaybackParametersConsecutivelyNotifiesListenerForEveryChangeOnce()
throws Exception {
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("setPlaybackParametersNotifiesListenerForEveryChangeOnce")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
- .setPlaybackParameters(new PlaybackParameters(1.1f))
- .setPlaybackParameters(new PlaybackParameters(1.2f))
- .setPlaybackParameters(new PlaybackParameters(1.3f))
+ .setPlaybackSpeed(1.1f)
+ .setPlaybackSpeed(1.2f)
+ .setPlaybackSpeed(1.3f)
.play()
.build();
- List<PlaybackParameters> reportedPlaybackParameters = new ArrayList<>();
+ List<Float> reportedPlaybackSpeeds = new ArrayList<>();
EventListener listener =
new EventListener() {
@Override
- public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
- reportedPlaybackParameters.add(playbackParameters);
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
+ reportedPlaybackSpeeds.add(playbackSpeed);
}
};
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.setEventListener(listener)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
- assertThat(reportedPlaybackParameters)
- .containsExactly(
- new PlaybackParameters(1.1f),
- new PlaybackParameters(1.2f),
- new PlaybackParameters(1.3f))
- .inOrder();
+ assertThat(reportedPlaybackSpeeds).containsExactly(1.1f, 1.2f, 1.3f).inOrder();
}
@Test
@@ -3052,59 +3166,55 @@
setUnsupportedPlaybackParametersConsecutivelyNotifiesListenerForEveryChangeOnceAndResetsOnceHandled()
throws Exception {
Renderer renderer =
- new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) {
+ new FakeMediaClockRenderer(C.TRACK_TYPE_AUDIO) {
@Override
public long getPositionUs() {
return 0;
}
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
+ public void setPlaybackSpeed(float playbackSpeed) {}
@Override
- public PlaybackParameters getPlaybackParameters() {
- return PlaybackParameters.DEFAULT;
+ public float getPlaybackSpeed() {
+ return Player.DEFAULT_PLAYBACK_SPEED;
}
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("setUnsupportedPlaybackParametersNotifiesListenersCorrectly")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
- .setPlaybackParameters(new PlaybackParameters(1.1f))
- .setPlaybackParameters(new PlaybackParameters(1.2f))
- .setPlaybackParameters(new PlaybackParameters(1.3f))
+ .setPlaybackSpeed(1.1f)
+ .setPlaybackSpeed(1.2f)
+ .setPlaybackSpeed(1.3f)
.play()
.build();
- List<PlaybackParameters> reportedPlaybackParameters = new ArrayList<>();
+ List<Float> reportedPlaybackParameters = new ArrayList<>();
EventListener listener =
new EventListener() {
@Override
- public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
- reportedPlaybackParameters.add(playbackParameters);
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
+ reportedPlaybackParameters.add(playbackSpeed);
}
};
- new ExoPlayerTestRunner.Builder()
- .setSupportedFormats(Builder.AUDIO_FORMAT)
+ new ExoPlayerTestRunner.Builder(context)
+ .setSupportedFormats(ExoPlayerTestRunner.AUDIO_FORMAT)
.setRenderers(renderer)
.setActionSchedule(actionSchedule)
.setEventListener(listener)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(reportedPlaybackParameters)
- .containsExactly(
- new PlaybackParameters(1.1f),
- new PlaybackParameters(1.2f),
- new PlaybackParameters(1.3f),
- PlaybackParameters.DEFAULT)
+ .containsExactly(1.1f, 1.2f, 1.3f, Player.DEFAULT_PLAYBACK_SPEED)
.inOrder();
}
@Test
public void simplePlaybackHasNoPlaybackSuppression() throws Exception {
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("simplePlaybackHasNoPlaybackSuppression")
+ new ActionSchedule.Builder(TAG)
.play()
.waitForPlaybackState(Player.STATE_READY)
.pause()
@@ -3119,10 +3229,10 @@
seenPlaybackSuppression.set(true);
}
};
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.setEventListener(listener)
- .build(context)
+ .build()
.start()
.blockUntilEnded(TIMEOUT_MS);
@@ -3136,7 +3246,7 @@
PlayerStateGrabber playerStateGrabber = new PlayerStateGrabber();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("audioFocusDenied")
+ new ActionSchedule.Builder(TAG)
.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true)
.play()
.waitForPlaybackState(Player.STATE_READY)
@@ -3151,10 +3261,10 @@
seenPlaybackSuppression.set(true);
}
};
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.setEventListener(listener)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
@@ -3163,7 +3273,7 @@
}
@Test
- public void testDelegatingMediaSourceApproach() throws Exception {
+ public void delegatingMediaSourceApproach() throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -3175,9 +3285,9 @@
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(mediaTransferListener);
underlyingSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT));
underlyingSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT));
prepareChildSource(null, underlyingSource);
}
@@ -3203,8 +3313,8 @@
return false;
}
- @Nullable
@Override
+ @Nullable
public Timeline getInitialTimeline() {
return Timeline.EMPTY;
}
@@ -3214,9 +3324,8 @@
long[] windowCounts = new long[1];
int seekToWindowIndex = 1;
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testDelegatingMediaSourceApproach")
+ new ActionSchedule.Builder(TAG)
.seek(/* windowIndex= */ 1, /* positionMs= */ 5000)
- .waitForSeekProcessed()
.waitForTimelineChanged(
/* expectedTimeline= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.executeRunnable(
@@ -3230,15 +3339,14 @@
})
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(delegatingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
assertArrayEquals(new long[] {2}, windowCounts);
assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices);
@@ -3246,14 +3354,14 @@
}
@Test
- public void testSeekTo_windowIndexIsReset_deprecated() throws Exception {
+ public void seekTo_windowIndexIsReset_deprecated() throws Exception {
FakeTimeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
final int[] windowIndex = {C.INDEX_UNSET};
final long[] positionMs = {C.TIME_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekTo_windowIndexIsReset_deprecated")
+ new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000)
@@ -3275,10 +3383,10 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(loopingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
@@ -3287,14 +3395,14 @@
}
@Test
- public void testSeekTo_windowIndexIsReset() throws Exception {
+ public void seekTo_windowIndexIsReset() throws Exception {
FakeTimeline fakeTimeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
final int[] windowIndex = {C.INDEX_UNSET};
final long[] positionMs = {C.TIME_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekTo_windowIndexIsReset")
+ new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000)
@@ -3315,10 +3423,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(loopingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
@@ -3332,7 +3440,7 @@
CountDownLatch becomingNoisyDelivered = new CountDownLatch(1);
PlayerStateGrabber playerStateGrabber = new PlayerStateGrabber();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -3353,7 +3461,7 @@
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder().setActionSchedule(actionSchedule).build(context).start();
+ new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start();
becomingNoisyHandlingDisabled.await();
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
becomingNoisyDelivered.countDown();
@@ -3366,7 +3474,7 @@
public void pausesWhenBecomingNoisyIfBecomingNoisyHandlingIsEnabled() throws Exception {
CountDownLatch becomingNoisyHandlingEnabled = new CountDownLatch(1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("pausesWhenBecomingNoisyIfBecomingNoisyHandlingIsEnabled")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -3380,7 +3488,7 @@
.build();
ExoPlayerTestRunner testRunner =
- new ExoPlayerTestRunner.Builder().setActionSchedule(actionSchedule).build(context).start();
+ new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start();
becomingNoisyHandlingEnabled.await();
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
@@ -3389,6 +3497,8 @@
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
}
+ // Disabled until the flag to throw exceptions for [internal: b/144538905] is enabled by default.
+ @Ignore
@Test
public void loadControlNeverWantsToLoad_throwsIllegalStateException() throws Exception {
LoadControl neverLoadingLoadControl =
@@ -3412,21 +3522,21 @@
MediaSource chunkedMediaSource =
new FakeAdaptiveMediaSource(
new FakeTimeline(/* windowCount= */ 1),
- new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)),
+ new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)),
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
- try {
- new ExoPlayerTestRunner.Builder()
- .setLoadControl(neverLoadingLoadControl)
- .setMediaSources(chunkedMediaSource)
- .build(context)
- .start()
- .blockUntilEnded(TIMEOUT_MS);
- fail();
- } catch (ExoPlaybackException e) {
- assertThat(e.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
- assertThat(e.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
- }
+ ExoPlaybackException exception =
+ assertThrows(
+ ExoPlaybackException.class,
+ () ->
+ new ExoPlayerTestRunner.Builder(context)
+ .setLoadControl(neverLoadingLoadControl)
+ .setMediaSources(chunkedMediaSource)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS));
+ assertThat(exception.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
+ assertThat(exception.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
}
@Test
@@ -3452,20 +3562,20 @@
MediaSource chunkedMediaSource =
new FakeAdaptiveMediaSource(
new FakeTimeline(/* windowCount= */ 1),
- new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)),
+ new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)),
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setLoadControl(neverLoadingOrPlayingLoadControl)
.setMediaSources(chunkedMediaSource)
- .build(context)
+ .build()
.start()
// This throws if playback doesn't finish within timeout.
.blockUntilEnded(TIMEOUT_MS);
}
@Test
- public void testMoveMediaItem() throws Exception {
+ public void moveMediaItem() throws Exception {
TimelineWindowDefinition firstWindowDefinition =
new TimelineWindowDefinition(
/* periodCount= */ 1,
@@ -3486,33 +3596,19 @@
MediaSource mediaSource2 = new FakeMediaSource(timeline2);
Timeline expectedDummyTimeline =
new FakeTimeline(
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 1,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 2,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE));
+ TimelineWindowDefinition.createDummy(/* tag= */ 1),
+ TimelineWindowDefinition.createDummy(/* tag= */ 2));
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testMoveMediaItem")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(
/* expectedTimeline= */ null, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.moveMediaItem(/* currentIndex= */ 0, /* newIndex= */ 1)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource1, mediaSource2)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -3529,7 +3625,7 @@
}
@Test
- public void testRemoveMediaItem() throws Exception {
+ public void removeMediaItem() throws Exception {
TimelineWindowDefinition firstWindowDefinition =
new TimelineWindowDefinition(
/* periodCount= */ 1,
@@ -3558,45 +3654,24 @@
MediaSource mediaSource2 = new FakeMediaSource(timeline2);
MediaSource mediaSource3 = new FakeMediaSource(timeline3);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRemoveMediaItems")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.removeMediaItem(/* index= */ 0)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource1, mediaSource2, mediaSource3)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
Timeline expectedDummyTimeline =
new FakeTimeline(
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 1,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 2,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 3,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE));
+ TimelineWindowDefinition.createDummy(/* tag= */ 1),
+ TimelineWindowDefinition.createDummy(/* tag= */ 2),
+ TimelineWindowDefinition.createDummy(/* tag= */ 3));
Timeline expectedRealTimeline =
new FakeTimeline(firstWindowDefinition, secondWindowDefinition, thirdWindowDefinition);
Timeline expectedRealTimelineAfterRemove =
@@ -3610,7 +3685,7 @@
}
@Test
- public void testRemoveMediaItems() throws Exception {
+ public void removeMediaItems() throws Exception {
TimelineWindowDefinition firstWindowDefinition =
new TimelineWindowDefinition(
/* periodCount= */ 1,
@@ -3639,45 +3714,24 @@
MediaSource mediaSource2 = new FakeMediaSource(timeline2);
MediaSource mediaSource3 = new FakeMediaSource(timeline3);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRemoveMediaItems")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 3)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource1, mediaSource2, mediaSource3)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
Timeline expectedDummyTimeline =
new FakeTimeline(
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 1,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 2,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 3,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE));
+ TimelineWindowDefinition.createDummy(/* tag= */ 1),
+ TimelineWindowDefinition.createDummy(/* tag= */ 2),
+ TimelineWindowDefinition.createDummy(/* tag= */ 3));
Timeline expectedRealTimeline =
new FakeTimeline(firstWindowDefinition, secondWindowDefinition, thirdWindowDefinition);
Timeline expectedRealTimelineAfterRemove = new FakeTimeline(firstWindowDefinition);
@@ -3690,26 +3744,26 @@
}
@Test
- public void testClearMediaItems() throws Exception {
+ public void clearMediaItems() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testClearMediaItems")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.waitForPlaybackState(Player.STATE_READY)
.clearMediaItems()
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
exoPlayerTestRunner.assertTimelinesSame(dummyTimeline, timeline, Timeline.EMPTY);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
@@ -3718,23 +3772,23 @@
}
@Test
- public void testMultipleModificationWithRecursiveListenerInvocations() throws Exception {
+ public void multipleModificationWithRecursiveListenerInvocations() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource mediaSource = new FakeMediaSource(timeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testMultipleModificationWithRecursiveListenerInvocations")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.clearMediaItems()
.setMediaSources(secondMediaSource)
.waitForTimelineChanged()
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -3754,8 +3808,7 @@
}
@Test
- public void testModifyPlaylistUnprepared_remainsInIdle_needsPrepareForBuffering()
- throws Exception {
+ public void modifyPlaylistUnprepared_remainsInIdle_needsPrepareForBuffering() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
@@ -3764,8 +3817,7 @@
int[] timelineWindowCounts = new int[4];
int[] maskingPlaybackState = {C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testModifyPlaylistUnprepared_remainsInIdle_needsPrepareForBuffering")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(dummyTimeline, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)
.executeRunnable(
new PlaybackStateCollector(/* index= */ 0, playbackStates, timelineWindowCounts))
@@ -3786,7 +3838,6 @@
.executeRunnable(
new PlaybackStateCollector(/* index= */ 3, playbackStates, timelineWindowCounts))
.seek(/* windowIndex= */ 1, /* positionMs= */ 2000)
- .waitForSeekProcessed()
.prepare()
// The first expected buffering state arrives after prepare but not before.
.waitForPlaybackState(Player.STATE_BUFFERING)
@@ -3794,10 +3845,10 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -3807,7 +3858,6 @@
playbackStates);
assertArrayEquals(new int[] {1, 0, 1, 2}, timelineWindowCounts);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_BUFFERING /* first buffering state after prepare */,
Player.STATE_READY,
Player.STATE_ENDED);
@@ -3819,22 +3869,8 @@
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source update after prepare */);
Timeline expectedSecondDummyTimeline =
new FakeTimeline(
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 0,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE),
- new TimelineWindowDefinition(
- /* periodCount= */ 1,
- /* id= */ 0,
- /* isSeekable= */ false,
- /* isDynamic= */ true,
- /* isLive= */ false,
- /* durationUs= */ C.TIME_UNSET,
- AdPlaybackState.NONE));
+ TimelineWindowDefinition.createDummy(/* tag= */ 0),
+ TimelineWindowDefinition.createDummy(/* tag= */ 0));
Timeline expectedSecondRealTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -3859,12 +3895,11 @@
}
@Test
- public void testModifyPlaylistPrepared_remainsInEnded_needsSeekForBuffering() throws Exception {
+ public void modifyPlaylistPrepared_remainsInEnded_needsSeekForBuffering() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource secondMediaSource = new FakeMediaSource(timeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testModifyPlaylistPrepared_remainsInEnded_needsSeekForBuffering")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged(timeline, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)
.waitForPlaybackState(Player.STATE_BUFFERING)
.waitForPlaybackState(Player.STATE_READY)
@@ -3876,23 +3911,21 @@
.addMediaSources(secondMediaSource) // add again to be able to test the seek
.waitForTimelineChanged()
.seek(/* positionMs= */ 2_000) // seek must transition to buffering
- .waitForSeekProcessed()
.waitForPlaybackState(Player.STATE_BUFFERING)
.waitForPlaybackState(Player.STATE_READY)
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 2)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_BUFFERING, // first buffering
Player.STATE_READY,
Player.STATE_ENDED, // clear playlist
@@ -3920,15 +3953,14 @@
}
@Test
- public void testStopWithNoReset_modifyingPlaylistRemainsInIdleState_needsPrepareForBuffering()
+ public void stopWithNoReset_modifyingPlaylistRemainsInIdleState_needsPrepareForBuffering()
throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource secondMediaSource = new FakeMediaSource(timeline);
int[] playbackStateHolder = new int[3];
int[] windowCountHolder = new int[3];
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testStopWithNoReset_modifyingPlaylistRemainsInIdleState_needsPrepareForBuffering")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ false)
.executeRunnable(
@@ -3945,10 +3977,10 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -3957,7 +3989,6 @@
new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE}, playbackStateHolder);
assertArrayEquals(new int[] {1, 0, 1}, windowCountHolder);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_BUFFERING, // first buffering
Player.STATE_READY,
Player.STATE_IDLE, // stop
@@ -3975,10 +4006,10 @@
}
@Test
- public void testPrepareWithInvalidInitialSeek_expectEndedImmediately() throws Exception {
+ public void prepareWithInvalidInitialSeek_expectEndedImmediately() throws Exception {
final int[] currentWindowIndices = {C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testPrepareWithInvalidInitialSeek_expectEnded")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
new PlayerRunnable() {
@@ -3990,40 +4021,37 @@
.prepare()
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
- exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_IDLE, Player.STATE_ENDED);
+ exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED);
exoPlayerTestRunner.assertTimelinesSame();
exoPlayerTestRunner.assertTimelineChangeReasonsEqual();
assertArrayEquals(new int[] {1}, currentWindowIndices);
}
@Test
- public void testPrepareWhenAlreadyPreparedIsANoop() throws Exception {
+ public void prepareWhenAlreadyPreparedIsANoop() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testPrepareWhenAlreadyPreparedIsANoop")
- .waitForPlaybackState(Player.STATE_READY)
- .prepare()
- .build();
+ new ActionSchedule.Builder(TAG).waitForPlaybackState(Player.STATE_READY).prepare().build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
exoPlayerTestRunner.assertTimelinesSame(dummyTimeline, timeline);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED /* media item set (masked timeline) */,
@@ -4031,7 +4059,7 @@
}
@Test
- public void testSeekToIndexLargerThanNumberOfPlaylistItems() throws Exception {
+ public void seekToIndexLargerThanNumberOfPlaylistItems() throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -4039,14 +4067,15 @@
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(
/* isAtomic= */ false,
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT),
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT));
int[] currentWindowIndices = new int[1];
long[] currentPlaybackPositions = new long[1];
int seekToWindowIndex = 1;
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekToIndexLargerThanNumberOfPlaylistItems")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ .waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4056,11 +4085,11 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.initialSeek(seekToWindowIndex, 5000)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4069,7 +4098,7 @@
}
@Test
- public void testSeekToIndexWithEmptyMultiWindowMediaSource() throws Exception {
+ public void seekToIndexWithEmptyMultiWindowMediaSource() throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -4081,8 +4110,8 @@
long[] windowCounts = new long[2];
int seekToWindowIndex = 1;
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekToIndexWithEmptyMultiWindowMediaSource")
- .waitForTimelineChanged()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4093,12 +4122,11 @@
}
})
.executeRunnable(
- () -> {
- concatenatingMediaSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
- concatenatingMediaSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
- })
+ () ->
+ concatenatingMediaSource.addMediaSources(
+ Arrays.asList(
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT))))
.waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@@ -4110,11 +4138,11 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.initialSeek(seekToWindowIndex, 5000)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4124,27 +4152,24 @@
}
@Test
- public void testEmptyMultiWindowMediaSource_doesNotEnterBufferState() throws Exception {
+ public void emptyMultiWindowMediaSource_doesNotEnterBufferState() throws Exception {
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(/* isAtomic= */ false);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testEmptyMultiWindowMediaSource_doesNotEnterBufferState")
- .waitForTimelineChanged()
- .build();
+ new ActionSchedule.Builder(TAG).waitForPlaybackState(Player.STATE_ENDED).build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
- exoPlayerTestRunner.assertPlaybackStatesEqual(1, 4);
+ exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED);
}
@Test
- public void testSeekToIndexWithEmptyMultiWindowMediaSource_usesLazyPreparation()
- throws Exception {
+ public void seekToIndexWithEmptyMultiWindowMediaSource_usesLazyPreparation() throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -4156,8 +4181,8 @@
long[] windowCounts = new long[2];
int seekToWindowIndex = 1;
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSeekToIndexWithEmptyMultiWindowMediaSource")
- .waitForTimelineChanged()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4168,12 +4193,11 @@
}
})
.executeRunnable(
- () -> {
- concatenatingMediaSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
- concatenatingMediaSource.addMediaSource(
- new FakeMediaSource(fakeTimeline, Builder.VIDEO_FORMAT));
- })
+ () ->
+ concatenatingMediaSource.addMediaSources(
+ Arrays.asList(
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT))))
.waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@@ -4185,12 +4209,12 @@
}
})
.build();
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setUseLazyPreparation(/* useLazyPreparation= */ true)
.initialSeek(seekToWindowIndex, 5000)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4200,12 +4224,94 @@
}
@Test
- public void testSetMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception {
+ public void
+ timelineUpdateInMultiWindowMediaSource_removingPeriod_withUnpreparedMaskingMediaPeriod_doesNotThrow()
+ throws Exception {
+ TimelineWindowDefinition window1 =
+ new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1);
+ TimelineWindowDefinition window2 =
+ new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2);
+ FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null);
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ // Wait so that the player can create its unprepared MaskingMediaPeriod.
+ .waitForPendingPlayerCommands()
+ // Let the player assign the unprepared period to window1.
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(new FakeTimeline(window1, window2)))
+ .waitForTimelineChanged()
+ // Remove window1 and assume the update is handled without throwing.
+ .executeRunnable(() -> mediaSource.setNewSourceInfo(new FakeTimeline(window2)))
+ .waitForTimelineChanged()
+ .stop()
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(mediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS);
+
+ // Assertion is to not throw while running the action schedule above.
+ }
+
+ @Test
+ public void setPlayWhenReady_keepsCurrentPosition() throws Exception {
+ AtomicLong positionAfterSetPlayWhenReady = new AtomicLong(C.TIME_UNSET);
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .playUntilPosition(0, 5000)
+ .play()
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ positionAfterSetPlayWhenReady.set(player.getCurrentPosition());
+ }
+ })
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(positionAfterSetPlayWhenReady.get()).isAtLeast(5000);
+ }
+
+ @Test
+ public void setShuffleOrder_keepsCurrentPosition() throws Exception {
+ AtomicLong positionAfterSetShuffleOrder = new AtomicLong(C.TIME_UNSET);
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .playUntilPosition(0, 5000)
+ .setShuffleOrder(new FakeShuffleOrder(/* length= */ 1))
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ positionAfterSetShuffleOrder.set(player.getCurrentPosition());
+ }
+ })
+ .play()
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(positionAfterSetShuffleOrder.get()).isAtLeast(5000);
+ }
+
+ @Test
+ public void setMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception {
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_empty_whenEmpty_correctMaskingWindowIndex")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4228,10 +4334,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4239,15 +4345,16 @@
}
@Test
- public void testSetMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingWindowIndex()
+ public void setMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingWindowIndex()
throws Exception {
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_empty_whenEmpty_validInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4270,11 +4377,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4282,15 +4389,16 @@
}
@Test
- public void testSetMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex()
+ public void setMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex()
throws Exception {
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_empty_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4313,11 +4421,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 4, C.TIME_UNSET)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4325,14 +4433,14 @@
}
@Test
- public void testSetMediaSources_whenEmpty_correctMaskingWindowIndex() throws Exception {
+ public void setMediaSources_whenEmpty_correctMaskingWindowIndex() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_whenEmpty_correctMaskingWindowIndex")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@@ -4377,10 +4485,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4388,7 +4496,7 @@
}
@Test
- public void testSetMediaSources_whenEmpty_validInitialSeek_correctMaskingWindowIndex()
+ public void setMediaSources_whenEmpty_validInitialSeek_correctMaskingWindowIndex()
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
@@ -4396,9 +4504,10 @@
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenEmpty_validInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4419,11 +4528,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4431,7 +4540,7 @@
}
@Test
- public void testSetMediaSources_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex()
+ public void setMediaSources_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex()
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
@@ -4439,9 +4548,10 @@
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4463,11 +4573,11 @@
})
.waitForPlaybackState(Player.STATE_ENDED)
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4475,14 +4585,14 @@
}
@Test
- public void testSetMediaSources_correctMaskingWindowIndex() throws Exception {
+ public void setMediaSources_correctMaskingWindowIndex() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_correctMaskingWindowIndex")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@@ -4503,10 +4613,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -4514,7 +4624,7 @@
}
@Test
- public void testSetMediaSources_whenIdle_correctMaskingPlaybackState() throws Exception {
+ public void setMediaSources_whenIdle_correctMaskingPlaybackState() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1, 1L);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, 2L);
@@ -4522,7 +4632,7 @@
final int[] maskingPlaybackStates = new int[4];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_whenIdle_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4563,34 +4673,33 @@
.prepare()
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
- exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_IDLE, Player.STATE_ENDED);
+ exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED);
assertArrayEquals(
new int[] {Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE, Player.STATE_IDLE},
maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
}
@Test
- public void testSetMediaSources_whenIdle_invalidSeek_correctMaskingPlaybackState()
- throws Exception {
+ public void setMediaSources_whenIdle_invalidSeek_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenIdle_invalidSeek_correctMaskingPlaybackState")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4606,31 +4715,29 @@
.waitForPlaybackState(Player.STATE_READY)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
}
@Test
- public void testSetMediaSources_whenIdle_noSeek_correctMaskingPlaybackState() throws Exception {
+ public void setMediaSources_whenIdle_noSeek_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenIdle_noSeek_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4645,16 +4752,16 @@
.waitForPlaybackState(Player.STATE_READY)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@@ -4662,13 +4769,11 @@
}
@Test
- public void testSetMediaSources_whenIdle_noSeekEmpty_correctMaskingPlaybackState()
- throws Exception {
+ public void setMediaSources_whenIdle_noSeekEmpty_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenIdle_noSeekEmpty_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -4683,25 +4788,24 @@
.waitForPlaybackState(Player.STATE_READY)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_IDLE}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
}
@Test
- public void testSetMediaSources_whenEnded_correctMaskingPlaybackState() throws Exception {
+ public void setMediaSources_whenEnded_correctMaskingPlaybackState() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1, 1L);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, 2L);
@@ -4709,7 +4813,7 @@
final int[] maskingPlaybackStates = new int[4];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_whenEnded_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
new PlayerRunnable() {
@@ -4755,17 +4859,16 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 3)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_ENDED,
Player.STATE_BUFFERING,
Player.STATE_READY,
@@ -4780,9 +4883,6 @@
maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@@ -4790,14 +4890,11 @@
}
@Test
- public void testSetMediaSources_whenEnded_invalidSeek_correctMaskingPlaybackState()
- throws Exception {
+ public void setMediaSources_whenEnded_invalidSeek_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenEnded_invalidSeek_correctMaskingPlaybackState")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
new PlayerRunnable() {
@@ -4814,30 +4911,28 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
- exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_IDLE, Player.STATE_ENDED);
+ exoPlayerTestRunner.assertPlaybackStatesEqual(Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
}
@Test
- public void testSetMediaSources_whenEnded_noSeek_correctMaskingPlaybackState() throws Exception {
+ public void setMediaSources_whenEnded_noSeek_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenEnded_noSeek_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.clearMediaItems()
.waitForPlaybackState(Player.STATE_ENDED)
@@ -4856,15 +4951,15 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@@ -4875,13 +4970,11 @@
}
@Test
- public void testSetMediaSources_whenEnded_noSeekEmpty_correctMaskingPlaybackState()
- throws Exception {
+ public void setMediaSources_whenEnded_noSeekEmpty_correctMaskingPlaybackState() throws Exception {
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenEnded_noSeekEmpty_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.clearMediaItems()
.waitForPlaybackState(Player.STATE_ENDED)
@@ -4897,25 +4990,24 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
}
@Test
- public void testSetMediaSources_whenPrepared_correctMaskingPlaybackState() throws Exception {
+ public void setMediaSources_whenPrepared_correctMaskingPlaybackState() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1, 1L);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, 2L);
@@ -4923,7 +5015,7 @@
final int[] maskingPlaybackStates = new int[4];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testSetMediaSources_whenPrepared_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
@@ -4981,18 +5073,16 @@
.waitForPlaybackState(Player.STATE_READY)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 3)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
- Player.STATE_IDLE, // Pause.
Player.STATE_BUFFERING,
Player.STATE_READY, // Ready after initial prepare.
Player.STATE_ENDED, // Ended after setting empty source without seek.
@@ -5005,7 +5095,6 @@
Player.STATE_READY, // Ready after setting media item with seek.
Player.STATE_BUFFERING,
Player.STATE_READY, // Ready again after re-setting source.
- Player.STATE_BUFFERING,
Player.STATE_BUFFERING, // Play.
Player.STATE_READY, // Ready after setting media item without seek.
Player.STATE_ENDED);
@@ -5032,15 +5121,14 @@
}
@Test
- public void testSetMediaSources_whenPrepared_invalidSeek_correctMaskingPlaybackState()
+ public void setMediaSources_whenPrepared_invalidSeek_correctMaskingPlaybackState()
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1, 1L);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
final int[] maskingPlaybackStates = new int[1];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testSetMediaSources_whenPrepared_invalidSeek_correctMaskingPlaybackState")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_ENDED)
.executeRunnable(
@@ -5060,42 +5148,39 @@
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setExpectedPlayerEndedCount(/* expectedPlayerEndedCount= */ 2)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(new ConcatenatingMediaSource())
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
- Player.STATE_IDLE, // Pause.
Player.STATE_ENDED, // Empty source has been prepared.
Player.STATE_BUFFERING, // After setting another source.
Player.STATE_READY,
- Player.STATE_READY, // Play.
Player.STATE_ENDED);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackStates);
exoPlayerTestRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
}
@Test
- public void testAddMediaSources_skipSettingMediaItems_validInitialSeek_correctMaskingWindowIndex()
+ public void addMediaSources_skipSettingMediaItems_validInitialSeek_correctMaskingWindowIndex()
throws Exception {
final int[] currentWindowIndices = new int[5];
Arrays.fill(currentWindowIndices, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testAddMediaSources_skipSettingMediaItems_validInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5122,11 +5207,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5141,9 +5226,10 @@
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testAddMediaSources_skipSettingMediaItems_invalidInitialSeek_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5163,11 +5249,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5175,7 +5261,7 @@
}
@Test
- public void testMoveMediaItems_correctMaskingWindowIndex() throws Exception {
+ public void moveMediaItems_correctMaskingWindowIndex() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(timeline);
MediaSource secondMediaSource = new FakeMediaSource(timeline);
@@ -5184,7 +5270,7 @@
C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET
};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testMoveMediaItems_correctMaskingWindowIndex")
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@@ -5205,7 +5291,6 @@
}
})
.seek(/* windowIndex= */ 2, C.TIME_UNSET)
- .waitForSeekProcessed()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5235,7 +5320,6 @@
}
})
.seek(/* windowIndex= */ 0, C.TIME_UNSET)
- .waitForSeekProcessed()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5247,10 +5331,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource, secondMediaSource, thirdMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5258,14 +5342,14 @@
}
@Test
- public void testMoveMediaItems_unprepared_correctMaskingWindowIndex() throws Exception {
+ public void moveMediaItems_unprepared_correctMaskingWindowIndex() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testMoveMediaItems_unprepared_correctMaskingWindowIndex")
+ new ActionSchedule.Builder(TAG)
.waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@@ -5287,10 +5371,10 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource, secondMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5298,15 +5382,15 @@
}
@Test
- public void testRemoveMediaItems_correctMaskingWindowIndex() throws Exception {
+ public void removeMediaItems_correctMaskingWindowIndex() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testRemoveMediaItems_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5318,11 +5402,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(firstMediaSource, secondMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5330,7 +5414,7 @@
}
@Test
- public void testRemoveMediaItems_currentItemRemoved_correctMaskingWindowIndex() throws Exception {
+ public void removeMediaItems_currentItemRemoved_correctMaskingWindowIndex() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
@@ -5338,9 +5422,8 @@
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testRemoveMediaItems_CurrentItemRemoved_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5354,11 +5437,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ 5000)
.setMediaSources(firstMediaSource, secondMediaSource, firstMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5368,8 +5451,7 @@
}
@Test
- public void testRemoveMediaItems_currentItemRemovedThatIsTheLast_correctMasking()
- throws Exception {
+ public void removeMediaItems_currentItemRemovedThatIsTheLast_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1, 1L);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, 2L);
@@ -5383,9 +5465,7 @@
final int[] maskingPlaybackStates = new int[4];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testRemoveMediaItems_CurrentItemRemovedThatIsTheLast_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@@ -5456,18 +5536,17 @@
})
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 2, /* positionMs= */ C.TIME_UNSET)
.setExpectedPlayerEndedCount(2)
.setMediaSources(firstMediaSource, secondMediaSource, thirdMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Expect reset of masking to first window.
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE,
Player.STATE_BUFFERING,
Player.STATE_READY, // Ready after initial prepare.
Player.STATE_ENDED, // ended after removing current window index
@@ -5486,35 +5565,34 @@
}
@Test
- public void testRemoveMediaItems_removeTailWithCurrentWindow_whenIdle_finishesPlayback()
+ public void removeMediaItems_removeTailWithCurrentWindow_whenIdle_finishesPlayback()
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder(
- "testRemoveMediaItems_removeTailWithCurrentWindow_whenIdle_finishesPlayback")
+ new ActionSchedule.Builder(TAG)
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
- .waitForSeekProcessed()
+ .waitForPendingPlayerCommands()
.removeMediaItem(/* index= */ 1)
.prepare()
.waitForPlaybackState(Player.STATE_ENDED)
.build();
ExoPlayerTestRunner exoPlayerTestRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(firstMediaSource, secondMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
exoPlayerTestRunner.assertPlaybackStatesEqual(
- Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
+ Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED);
}
@Test
- public void testClearMediaItems_correctMasking() throws Exception {
+ public void clearMediaItems_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
@@ -5522,8 +5600,8 @@
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final int[] maskingPlaybackState = {C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testClearMediaItems_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5535,11 +5613,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(firstMediaSource, secondMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5548,7 +5626,7 @@
}
@Test
- public void testClearMediaItems_unprepared_correctMaskingWindowIndex_notEnded() throws Exception {
+ public void clearMediaItems_unprepared_correctMaskingWindowIndex_notEnded() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
@@ -5556,8 +5634,10 @@
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final int[] currentStates = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("testClearMediaItems_correctMaskingWindowIndex")
- .waitForSeekProcessed()
+ new ActionSchedule.Builder(TAG)
+ // Wait for initial seek to be fully handled by internal player.
+ .waitForPositionDiscontinuity()
+ .waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5579,11 +5659,11 @@
}
})
.build();
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.setMediaSources(firstMediaSource, secondMediaSource)
.setActionSchedule(actionSchedule)
- .build(context)
+ .build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -5592,26 +5672,32 @@
assertArrayEquals(new int[] {1, 0}, currentWindowIndices);
}
+ // TODO(b/150584930): Fix reporting of renderer errors.
+ @Ignore
@Test
- public void errorThrownDuringPeriodTransition_keepsConsistentPlayerState() {
+ public void errorThrownDuringRendererEnableAtPeriodTransition_isReportedForNewPeriod() {
FakeMediaSource source1 =
- new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.VIDEO_FORMAT);
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
FakeMediaSource source2 =
- new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer =
- new FakeRenderer(Builder.AUDIO_FORMAT) {
+ new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the period transition.
- throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
+ throw createRendererException(
+ new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
};
AtomicReference<TrackGroupArray> trackGroupsAfterError = new AtomicReference<>();
AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
AtomicInteger windowIndexAfterError = new AtomicInteger();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("errorThrownDuringPeriodTransition_keepsConsistentPlayerState")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5628,20 +5714,13 @@
});
}
})
- .pause()
- // Wait until fully buffered so that the new renderer can be enabled immediately.
- .waitForIsLoading(true)
- .waitForIsLoading(false)
- .waitForIsLoading(true)
- .waitForIsLoading(false)
- .play()
.build();
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(source1, source2)
.setActionSchedule(actionSchedule)
.setRenderers(videoRenderer, audioRenderer)
- .build(context);
+ .build();
assertThrows(
ExoPlaybackException.class,
@@ -5653,7 +5732,139 @@
assertThat(windowIndexAfterError.get()).isEqualTo(1);
assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
- assertThat(trackGroupsAfterError.get().get(0).getFormat(0)).isEqualTo(Builder.AUDIO_FORMAT);
+ assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
+ assertThat(trackSelectionsAfterError.get().get(0)).isNull(); // Video renderer.
+ assertThat(trackSelectionsAfterError.get().get(1)).isNotNull(); // Audio renderer.
+ }
+
+ @Test
+ public void errorThrownDuringRendererDisableAtPeriodTransition_isReportedForCurrentPeriod() {
+ FakeMediaSource source1 =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
+ FakeMediaSource source2 =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer =
+ new FakeRenderer(C.TRACK_TYPE_VIDEO) {
+ @Override
+ protected void onStopped() throws ExoPlaybackException {
+ // Fail when stopping the renderer. This will happen during the period transition.
+ throw createRendererException(
+ new IllegalStateException(), ExoPlayerTestRunner.VIDEO_FORMAT);
+ }
+ };
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
+ AtomicReference<TrackGroupArray> trackGroupsAfterError = new AtomicReference<>();
+ AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
+ AtomicInteger windowIndexAfterError = new AtomicInteger();
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ player.addAnalyticsListener(
+ new AnalyticsListener() {
+ @Override
+ public void onPlayerError(
+ EventTime eventTime, ExoPlaybackException error) {
+ trackGroupsAfterError.set(player.getCurrentTrackGroups());
+ trackSelectionsAfterError.set(player.getCurrentTrackSelections());
+ windowIndexAfterError.set(player.getCurrentWindowIndex());
+ }
+ });
+ }
+ })
+ .build();
+ ExoPlayerTestRunner testRunner =
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(source1, source2)
+ .setActionSchedule(actionSchedule)
+ .setRenderers(videoRenderer, audioRenderer)
+ .build();
+
+ assertThrows(
+ ExoPlaybackException.class,
+ () ->
+ testRunner
+ .start(/* doPrepare= */ true)
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS));
+
+ assertThat(windowIndexAfterError.get()).isEqualTo(0);
+ assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
+ assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.VIDEO_FORMAT);
+ assertThat(trackSelectionsAfterError.get().get(0)).isNotNull(); // Video renderer.
+ assertThat(trackSelectionsAfterError.get().get(1)).isNull(); // Audio renderer.
+ }
+
+ // TODO(b/150584930): Fix reporting of renderer errors.
+ @Ignore
+ @Test
+ public void errorThrownDuringRendererReplaceStreamAtPeriodTransition_isReportedForNewPeriod() {
+ FakeMediaSource source1 =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeMediaSource source2 =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer =
+ new FakeRenderer(C.TRACK_TYPE_AUDIO) {
+ @Override
+ protected void onStreamChanged(Format[] formats, long offsetUs)
+ throws ExoPlaybackException {
+ // Fail when changing streams. This will happen during the period transition.
+ throw createRendererException(
+ new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
+ }
+ };
+ AtomicReference<TrackGroupArray> trackGroupsAfterError = new AtomicReference<>();
+ AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
+ AtomicInteger windowIndexAfterError = new AtomicInteger();
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ player.addAnalyticsListener(
+ new AnalyticsListener() {
+ @Override
+ public void onPlayerError(
+ EventTime eventTime, ExoPlaybackException error) {
+ trackGroupsAfterError.set(player.getCurrentTrackGroups());
+ trackSelectionsAfterError.set(player.getCurrentTrackSelections());
+ windowIndexAfterError.set(player.getCurrentWindowIndex());
+ }
+ });
+ }
+ })
+ .build();
+ ExoPlayerTestRunner testRunner =
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(source1, source2)
+ .setActionSchedule(actionSchedule)
+ .setRenderers(videoRenderer, audioRenderer)
+ .build();
+
+ assertThrows(
+ ExoPlaybackException.class,
+ () ->
+ testRunner
+ .start(/* doPrepare= */ true)
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS));
+
+ assertThat(windowIndexAfterError.get()).isEqualTo(1);
+ assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
+ assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
assertThat(trackSelectionsAfterError.get().get(0)).isNull(); // Video renderer.
assertThat(trackSelectionsAfterError.get().get(1)).isNotNull(); // Audio renderer.
}
@@ -5661,16 +5872,25 @@
@Test
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
FakeMediaSource source1 =
- new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.VIDEO_FORMAT);
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT);
FakeMediaSource source2 =
- new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.AUDIO_FORMAT);
- FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
+ AtomicInteger audioRendererEnableCount = new AtomicInteger(0);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer =
- new FakeRenderer(Builder.AUDIO_FORMAT) {
+ new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- // Fail when enabling the renderer. This will happen during the playlist update.
- throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ if (audioRendererEnableCount.incrementAndGet() == 2) {
+ // Fail when enabling the renderer for the second time during the playlist update.
+ throw createRendererException(
+ new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
+ }
}
};
AtomicReference<Timeline> timelineAfterError = new AtomicReference<>();
@@ -5678,7 +5898,7 @@
AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
AtomicInteger windowIndexAfterError = new AtomicInteger();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -5705,11 +5925,11 @@
.removeMediaItem(0)
.build();
ExoPlayerTestRunner testRunner =
- new Builder()
+ new ExoPlayerTestRunner.Builder(context)
.setMediaSources(source1, source2)
.setActionSchedule(actionSchedule)
.setRenderers(videoRenderer, audioRenderer)
- .build(context);
+ .build();
assertThrows(
ExoPlaybackException.class,
@@ -5722,11 +5942,359 @@
assertThat(timelineAfterError.get().getWindowCount()).isEqualTo(1);
assertThat(windowIndexAfterError.get()).isEqualTo(0);
assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
- assertThat(trackGroupsAfterError.get().get(0).getFormat(0)).isEqualTo(Builder.AUDIO_FORMAT);
+ assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
+ .isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
assertThat(trackSelectionsAfterError.get().get(0)).isNull(); // Video renderer.
assertThat(trackSelectionsAfterError.get().get(1)).isNotNull(); // Audio renderer.
}
+ @Test
+ public void seekToCurrentPosition_inEndedState_switchesToBufferingStateAndContinuesPlayback()
+ throws Exception {
+ MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount = */ 1));
+ AtomicInteger windowIndexAfterFinalEndedState = new AtomicInteger();
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_ENDED)
+ .addMediaSources(mediaSource)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ player.seekTo(player.getCurrentPosition());
+ }
+ })
+ .waitForPlaybackState(Player.STATE_READY)
+ .waitForPlaybackState(Player.STATE_ENDED)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ windowIndexAfterFinalEndedState.set(player.getCurrentWindowIndex());
+ }
+ })
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(mediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(windowIndexAfterFinalEndedState.get()).isEqualTo(1);
+ }
+
+ @Test
+ public void pauseAtEndOfMediaItems_pausesPlaybackBeforeTransitioningToTheNextItem()
+ throws Exception {
+ TimelineWindowDefinition timelineWindowDefinition =
+ new TimelineWindowDefinition(
+ /* isSeekable= */ true,
+ /* isDynamic= */ false,
+ /* durationUs= */ 10 * C.MICROS_PER_SECOND);
+ MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition));
+ AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET);
+ AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET);
+ AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET);
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .waitForPlayWhenReady(true)
+ .waitForPlayWhenReady(false)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ playbackStateAfterPause.set(player.getPlaybackState());
+ windowIndexAfterPause.set(player.getCurrentWindowIndex());
+ positionAfterPause.set(player.getContentPosition());
+ }
+ })
+ .play()
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setPauseAtEndOfMediaItems(true)
+ .setMediaSources(mediaSource, mediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_READY);
+ assertThat(windowIndexAfterPause.get()).isEqualTo(0);
+ assertThat(positionAfterPause.get()).isEqualTo(10_000);
+ }
+
+ @Test
+ public void pauseAtEndOfMediaItems_pausesPlaybackWhenEnded() throws Exception {
+ TimelineWindowDefinition timelineWindowDefinition =
+ new TimelineWindowDefinition(
+ /* isSeekable= */ true,
+ /* isDynamic= */ false,
+ /* durationUs= */ 10 * C.MICROS_PER_SECOND);
+ MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(timelineWindowDefinition));
+ AtomicInteger playbackStateAfterPause = new AtomicInteger(C.INDEX_UNSET);
+ AtomicLong positionAfterPause = new AtomicLong(C.TIME_UNSET);
+ AtomicInteger windowIndexAfterPause = new AtomicInteger(C.INDEX_UNSET);
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .waitForPlayWhenReady(true)
+ .waitForPlayWhenReady(false)
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ playbackStateAfterPause.set(player.getPlaybackState());
+ windowIndexAfterPause.set(player.getCurrentWindowIndex());
+ positionAfterPause.set(player.getContentPosition());
+ }
+ })
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setPauseAtEndOfMediaItems(true)
+ .setMediaSources(mediaSource)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(playbackStateAfterPause.get()).isEqualTo(Player.STATE_ENDED);
+ assertThat(windowIndexAfterPause.get()).isEqualTo(0);
+ assertThat(positionAfterPause.get()).isEqualTo(10_000);
+ }
+
+ // Disabled until the flag to throw exceptions for [internal: b/144538905] is enabled by default.
+ @Ignore
+ @Test
+ public void
+ infiniteLoading_withSmallAllocations_oomIsPreventedByLoadControl_andThrowsStuckBufferingIllegalStateException() {
+ MediaSource continuouslyAllocatingMediaSource =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT) {
+ @Override
+ protected FakeMediaPeriod createFakeMediaPeriod(
+ MediaPeriodId id,
+ TrackGroupArray trackGroupArray,
+ Allocator allocator,
+ EventDispatcher eventDispatcher,
+ @Nullable TransferListener transferListener) {
+ return new FakeMediaPeriod(trackGroupArray, eventDispatcher) {
+
+ private final List<Allocation> allocations = new ArrayList<>();
+
+ private Callback callback;
+
+ @Override
+ public synchronized void prepare(Callback callback, long positionUs) {
+ this.callback = callback;
+ super.prepare(callback, positionUs);
+ }
+
+ @Override
+ public long getBufferedPositionUs() {
+ // Pretend not to make loading progress, so that continueLoading keeps being called.
+ return 0;
+ }
+
+ @Override
+ public long getNextLoadPositionUs() {
+ // Pretend not to make loading progress, so that continueLoading keeps being called.
+ return 0;
+ }
+
+ @Override
+ public boolean continueLoading(long positionUs) {
+ allocations.add(allocator.allocate());
+ callback.onContinueLoadingRequested(this);
+ return true;
+ }
+ };
+ }
+ };
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ // Prevent player from ever assuming it finished playing.
+ .setRepeatMode(Player.REPEAT_MODE_ALL)
+ .build();
+ ExoPlayerTestRunner testRunner =
+ new ExoPlayerTestRunner.Builder(context)
+ .setActionSchedule(actionSchedule)
+ .setMediaSources(continuouslyAllocatingMediaSource)
+ .build();
+
+ ExoPlaybackException exception =
+ assertThrows(
+ ExoPlaybackException.class, () -> testRunner.start().blockUntilEnded(TIMEOUT_MS));
+ assertThat(exception.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
+ assertThat(exception.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void loading_withLargeAllocationCausingOom_playsRemainingMediaAndThenThrows() {
+ Loader.Loadable loadable =
+ new Loader.Loadable() {
+ @SuppressWarnings("UnusedVariable")
+ @Override
+ public void load() throws IOException {
+ @SuppressWarnings("unused") // This test needs the allocation to cause an OOM.
+ byte[] largeBuffer = new byte[Integer.MAX_VALUE];
+ }
+
+ @Override
+ public void cancelLoad() {}
+ };
+ MediaSource largeBufferAllocatingMediaSource =
+ new FakeMediaSource(
+ new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT) {
+ @Override
+ protected FakeMediaPeriod createFakeMediaPeriod(
+ MediaPeriodId id,
+ TrackGroupArray trackGroupArray,
+ Allocator allocator,
+ EventDispatcher eventDispatcher,
+ @Nullable TransferListener transferListener) {
+ return new FakeMediaPeriod(trackGroupArray, eventDispatcher) {
+ private Loader loader = new Loader("oomLoader");
+
+ @Override
+ public boolean continueLoading(long positionUs) {
+ loader.startLoading(
+ loadable, new DummyLoaderCallback(), /* defaultMinRetryCount= */ 1);
+ return true;
+ }
+
+ @Override
+ protected SampleStream createSampleStream(
+ long positionUs, TrackSelection selection, EventDispatcher eventDispatcher) {
+ // Create 3 samples without end of stream signal to test that all 3 samples are
+ // still played before the exception is thrown.
+ return new FakeSampleStream(
+ selection.getSelectedFormat(),
+ eventDispatcher,
+ positionUs,
+ /* timeUsIncrement= */ 0,
+ new FakeSampleStream.FakeSampleStreamItem(new byte[] {0}),
+ new FakeSampleStream.FakeSampleStreamItem(new byte[] {0}),
+ new FakeSampleStream.FakeSampleStreamItem(new byte[] {0})) {
+
+ @Override
+ public void maybeThrowError() throws IOException {
+ loader.maybeThrowError();
+ }
+ };
+ }
+ };
+ }
+ };
+ FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ ExoPlayerTestRunner testRunner =
+ new ExoPlayerTestRunner.Builder(context)
+ .setMediaSources(largeBufferAllocatingMediaSource)
+ .setRenderers(renderer)
+ .build();
+
+ ExoPlaybackException exception =
+ assertThrows(
+ ExoPlaybackException.class, () -> testRunner.start().blockUntilEnded(TIMEOUT_MS));
+ assertThat(exception.type).isEqualTo(ExoPlaybackException.TYPE_SOURCE);
+ assertThat(exception.getSourceException()).isInstanceOf(Loader.UnexpectedLoaderException.class);
+ assertThat(exception.getSourceException().getCause()).isInstanceOf(OutOfMemoryError.class);
+
+ assertThat(renderer.sampleBufferReadCount).isEqualTo(3);
+ }
+
+ @Test
+ public void seekTo_whileReady_callsOnIsPlayingChanged() throws Exception {
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .waitForPlaybackState(Player.STATE_READY)
+ .seek(/* positionMs= */ 0)
+ .waitForPlaybackState(Player.STATE_ENDED)
+ .build();
+ List<Boolean> onIsPlayingChanges = new ArrayList<>();
+ Player.EventListener eventListener =
+ new Player.EventListener() {
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ onIsPlayingChanges.add(isPlaying);
+ }
+ };
+ new ExoPlayerTestRunner.Builder(context)
+ .setEventListener(eventListener)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(onIsPlayingChanges).containsExactly(true, false, true, false).inOrder();
+ }
+
+ @Test
+ public void multipleListenersAndMultipleCallbacks_callbacksAreOrderedByType() throws Exception {
+ String playWhenReadyChange1 = "playWhenReadyChange1";
+ String playWhenReadyChange2 = "playWhenReadyChange2";
+ String isPlayingChange1 = "isPlayingChange1";
+ String isPlayingChange2 = "isPlayingChange2";
+ ArrayList<String> events = new ArrayList<>();
+ Player.EventListener eventListener1 =
+ new Player.EventListener() {
+ @Override
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ events.add(playWhenReadyChange1);
+ }
+
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ events.add(isPlayingChange1);
+ }
+ };
+ Player.EventListener eventListener2 =
+ new Player.EventListener() {
+ @Override
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ events.add(playWhenReadyChange2);
+ }
+
+ @Override
+ public void onIsPlayingChanged(boolean isPlaying) {
+ events.add(isPlayingChange2);
+ }
+ };
+ ActionSchedule actionSchedule =
+ new ActionSchedule.Builder(TAG)
+ .pause()
+ .executeRunnable(
+ new PlayerRunnable() {
+ @Override
+ public void run(SimpleExoPlayer player) {
+ player.addListener(eventListener1);
+ player.addListener(eventListener2);
+ }
+ })
+ .waitForPlaybackState(Player.STATE_READY)
+ .play()
+ .waitForPlaybackState(Player.STATE_ENDED)
+ .build();
+ new ExoPlayerTestRunner.Builder(context)
+ .setActionSchedule(actionSchedule)
+ .build()
+ .start()
+ .blockUntilActionScheduleFinished(TIMEOUT_MS)
+ .blockUntilEnded(TIMEOUT_MS);
+
+ assertThat(events)
+ .containsExactly(
+ playWhenReadyChange1,
+ playWhenReadyChange2,
+ isPlayingChange1,
+ isPlayingChange2,
+ isPlayingChange1,
+ isPlayingChange2)
+ .inOrder();
+ }
+
// Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
@@ -5756,6 +6324,16 @@
// Internal classes.
+ private static final class CountingMessageTarget implements PlayerMessage.Target {
+
+ public int messageCount;
+
+ @Override
+ public void handleMessage(int x, @Nullable Object message) {
+ messageCount++;
+ }
+ }
+
private static final class PositionGrabbingMessageTarget extends PlayerTarget {
public int windowIndex;
@@ -5769,10 +6347,8 @@
@Override
public void handleMessage(SimpleExoPlayer player, int messageType, @Nullable Object message) {
- if (player != null) {
- windowIndex = player.getCurrentWindowIndex();
- positionMs = player.getCurrentPosition();
- }
+ windowIndex = player.getCurrentWindowIndex();
+ positionMs = player.getCurrentPosition();
messageCount++;
}
}
@@ -5824,4 +6400,24 @@
timelineWindowCount[index] = player.getCurrentTimeline().getWindowCount();
}
}
+
+ private static final class DummyLoaderCallback implements Loader.Callback<Loader.Loadable> {
+ @Override
+ public void onLoadCompleted(
+ Loader.Loadable loadable, long elapsedRealtimeMs, long loadDurationMs) {}
+
+ @Override
+ public void onLoadCanceled(
+ Loader.Loadable loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {}
+
+ @Override
+ public Loader.LoadErrorAction onLoadError(
+ Loader.Loadable loadable,
+ long elapsedRealtimeMs,
+ long loadDurationMs,
+ IOException error,
+ int errorCount) {
+ return Loader.RETRY;
+ }
+ }
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
index 6ff5d66..ccc5156 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -27,6 +28,8 @@
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
+import com.google.android.exoplayer2.testutil.FakeTimeline;
+import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
@@ -35,8 +38,10 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.annotation.LooperMode;
/** Unit tests for {@link MediaPeriodQueue}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public final class MediaPeriodQueueTest {
@@ -52,20 +57,20 @@
private MediaPeriodQueue mediaPeriodQueue;
private AdPlaybackState adPlaybackState;
- private Object periodUid;
+ private Object firstPeriodUid;
private PlaybackInfo playbackInfo;
private RendererCapabilities[] rendererCapabilities;
private TrackSelector trackSelector;
private Allocator allocator;
- private Playlist playlist;
+ private MediaSourceList mediaSourceList;
private FakeMediaSource fakeMediaSource;
- private Playlist.MediaSourceHolder mediaSourceHolder;
+ private MediaSourceList.MediaSourceHolder mediaSourceHolder;
@Before
public void setUp() {
mediaPeriodQueue = new MediaPeriodQueue();
- playlist = mock(Playlist.class);
+ mediaSourceList = mock(MediaSourceList.class);
rendererCapabilities = new RendererCapabilities[0];
trackSelector = mock(TrackSelector.class);
allocator = mock(Allocator.class);
@@ -73,37 +78,46 @@
@Test
public void getNextMediaPeriodInfo_withoutAds_returnsLastMediaPeriodInfo() {
- setupTimeline();
+ setupAdTimeline(/* no ad groups */ );
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ true,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
@Test
public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() {
- setupTimeline(/* adGroupTimesUs...= */ 0);
+ setupAdTimeline(/* adGroupTimesUs...= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 0);
- assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ 0);
+ assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ C.TIME_UNSET);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ true,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
@Test
public void getNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ FIRST_AD_START_TIME_US,
/* durationUs= */ FIRST_AD_START_TIME_US,
- /* isLast= */ false,
+ /* isLastInPeriod= */ false,
+ /* isLastInWindow= */ false,
/* nextAdGroupIndex= */ 0);
// The next media period info should be null as we haven't loaded the ad yet.
advance();
@@ -113,10 +127,13 @@
/* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ FIRST_AD_START_TIME_US,
+ /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US,
/* endPositionUs= */ SECOND_AD_START_TIME_US,
/* durationUs= */ SECOND_AD_START_TIME_US,
- /* isLast= */ false,
+ /* isLastInPeriod= */ false,
+ /* isLastInWindow= */ false,
/* nextAdGroupIndex= */ 1);
advance();
setAdGroupLoaded(/* adGroupIndex= */ 1);
@@ -124,21 +141,27 @@
/* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ SECOND_AD_START_TIME_US,
+ /* requestedContentPositionUs= */ SECOND_AD_START_TIME_US,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ true,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
@Test
public void getNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPeriodInfos() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ FIRST_AD_START_TIME_US,
/* durationUs= */ FIRST_AD_START_TIME_US,
- /* isLast= */ false,
+ /* isLastInPeriod= */ false,
+ /* isLastInWindow= */ false,
/* nextAdGroupIndex= */ 0);
advance();
setAdGroupLoaded(/* adGroupIndex= */ 0);
@@ -146,10 +169,13 @@
/* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ FIRST_AD_START_TIME_US,
+ /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US,
/* endPositionUs= */ C.TIME_END_OF_SOURCE,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ false,
+ /* isLastInPeriod= */ false,
+ /* isLastInWindow= */ false,
/* nextAdGroupIndex= */ 1);
advance();
setAdGroupLoaded(/* adGroupIndex= */ 1);
@@ -157,36 +183,78 @@
/* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
- /* startPositionUs= */ CONTENT_DURATION_US,
+ /* periodUid= */ firstPeriodUid,
+ /* startPositionUs= */ CONTENT_DURATION_US - 1,
+ /* requestedContentPositionUs= */ CONTENT_DURATION_US,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ true,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
@Test
public void getNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() {
- setupTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);
+ setupAdTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ C.TIME_END_OF_SOURCE,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ false,
+ /* isLastInPeriod= */ false,
+ /* isLastInWindow= */ false,
/* nextAdGroupIndex= */ 0);
advance();
setAdGroupFailedToLoad(/* adGroupIndex= */ 0);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
- /* startPositionUs= */ CONTENT_DURATION_US,
+ /* periodUid= */ firstPeriodUid,
+ /* startPositionUs= */ CONTENT_DURATION_US - 1,
+ /* requestedContentPositionUs= */ CONTENT_DURATION_US,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ CONTENT_DURATION_US,
- /* isLast= */ true,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
+ /* nextAdGroupIndex= */ C.INDEX_UNSET);
+ }
+
+ @Test
+ public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() {
+ setupTimeline(
+ new FakeTimeline(
+ new TimelineWindowDefinition(
+ /* periodCount= */ 2,
+ /* id= */ new Object(),
+ /* isSeekable= */ false,
+ /* isDynamic= */ false,
+ /* durationUs= */ 2 * CONTENT_DURATION_US)));
+
+ assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 0),
+ /* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
+ /* endPositionUs= */ C.TIME_UNSET,
+ /* durationUs= */ CONTENT_DURATION_US
+ + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ false,
+ /* nextAdGroupIndex= */ C.INDEX_UNSET);
+ advance();
+ assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ /* periodUid= */ playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1),
+ /* startPositionUs= */ 0,
+ /* requestedContentPositionUs= */ 0,
+ /* endPositionUs= */ C.TIME_UNSET,
+ /* durationUs= */ CONTENT_DURATION_US,
+ /* isLastInPeriod= */ true,
+ /* isLastInWindow= */ true,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
}
@Test
public void
updateQueuedPeriods_withDurationChangeAfterReadingPeriod_handlesChangeAndRemovesPeriodsAfterChangedPeriod() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
@@ -211,7 +279,7 @@
@Test
public void
updateQueuedPeriods_withDurationChangeBeforeReadingPeriod_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
@@ -239,7 +307,7 @@
@Test
public void
updateQueuedPeriods_withDurationChangeInReadingPeriodAfterReadingPosition_handlesChangeAndRemovesPeriodsAfterChangedPeriod() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
@@ -269,7 +337,7 @@
@Test
public void
updateQueuedPeriods_withDurationChangeInReadingPeriodBeforeReadingPosition_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
@@ -299,7 +367,7 @@
@Test
public void
updateQueuedPeriods_withDurationChangeInReadingPeriodReadToEnd_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() {
- setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
+ setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
@@ -325,31 +393,36 @@
assertThat(getQueueLength()).isEqualTo(3);
}
- private void setupTimeline(long... adGroupTimesUs) {
+ private void setupAdTimeline(long... adGroupTimesUs) {
adPlaybackState =
new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US);
-
- // Create a media source holder.
SinglePeriodAdTimeline adTimeline =
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
- fakeMediaSource = new FakeMediaSource(adTimeline);
- mediaSourceHolder = new Playlist.MediaSourceHolder(fakeMediaSource, false);
+ setupTimeline(adTimeline);
+ }
+
+ private void setupTimeline(Timeline timeline) {
+ fakeMediaSource = new FakeMediaSource(timeline);
+ mediaSourceHolder = new MediaSourceList.MediaSourceHolder(fakeMediaSource, false);
mediaSourceHolder.mediaSource.prepareSourceInternal(/* mediaTransferListener */ null);
- Timeline timeline = createPlaylistTimeline();
- periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0);
+ Timeline playlistTimeline = createPlaylistTimeline();
+ firstPeriodUid = playlistTimeline.getUidOfPeriod(/* periodIndex= */ 0);
playbackInfo =
new PlaybackInfo(
- timeline,
- mediaPeriodQueue.resolveMediaPeriodIdForAds(timeline, periodUid, /* positionUs= */ 0),
- /* contentPositionUs= */ 0,
+ playlistTimeline,
+ mediaPeriodQueue.resolveMediaPeriodIdForAds(
+ playlistTimeline, firstPeriodUid, /* positionUs= */ 0),
+ /* requestedContentPositionUs= */ C.TIME_UNSET,
Player.STATE_READY,
/* playbackError= */ null,
/* isLoading= */ false,
/* trackGroups= */ null,
/* trackSelectorResult= */ null,
/* loadingMediaPeriodId= */ null,
+ /* playWhenReady= */ false,
+ Player.PLAYBACK_SUPPRESSION_REASON_NONE,
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0);
@@ -364,12 +437,12 @@
private void updateTimeline() {
SinglePeriodAdTimeline adTimeline =
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
- fakeMediaSource.setNewSourceInfo(adTimeline, /* manifest */ null);
+ fakeMediaSource.setNewSourceInfo(adTimeline);
playbackInfo = playbackInfo.copyWithTimeline(createPlaylistTimeline());
}
- private Playlist.PlaylistTimeline createPlaylistTimeline() {
- return new Playlist.PlaylistTimeline(
+ private MediaSourceList.PlaylistTimeline createPlaylistTimeline() {
+ return new MediaSourceList.PlaylistTimeline(
Collections.singleton(mediaSourceHolder),
new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
}
@@ -394,7 +467,7 @@
rendererCapabilities,
trackSelector,
allocator,
- playlist,
+ mediaSourceList,
getNextMediaPeriodInfo(),
new TrackSelectorResult(
new RendererConfiguration[0], new TrackSelection[0], /* info= */ null));
@@ -427,21 +500,25 @@
}
private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
+ Object periodUid,
long startPositionUs,
+ long requestedContentPositionUs,
long endPositionUs,
long durationUs,
- boolean isLast,
+ boolean isLastInPeriod,
+ boolean isLastInWindow,
int nextAdGroupIndex) {
assertThat(getNextMediaPeriodInfo())
.isEqualTo(
new MediaPeriodInfo(
new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, nextAdGroupIndex),
startPositionUs,
- /* contentPositionUs= */ C.TIME_UNSET,
+ requestedContentPositionUs,
endPositionUs,
durationUs,
- /* isLastInTimelinePeriod= */ isLast,
- /* isFinal= */ isLast));
+ isLastInPeriod,
+ isLastInWindow,
+ /* isFinal= */ isLastInWindow));
}
private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) {
@@ -449,7 +526,7 @@
.isEqualTo(
new MediaPeriodInfo(
new MediaPeriodId(
- periodUid,
+ firstPeriodUid,
adGroupIndex,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0),
@@ -458,6 +535,7 @@
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ AD_DURATION_US,
/* isLastInTimelinePeriod= */ false,
+ /* isLastInTimelineWindow= */ false,
/* isFinal= */ false));
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java
new file mode 100644
index 0000000..7ece4f3
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/MediaSourceListTest.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ShuffleOrder;
+import com.google.android.exoplayer2.testutil.FakeMediaSource;
+import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
+import com.google.android.exoplayer2.testutil.FakeTimeline;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link MediaSourceList}. */
+@RunWith(AndroidJUnit4.class)
+public class MediaSourceListTest {
+
+ private static final int MEDIA_SOURCE_LIST_SIZE = 4;
+
+ private MediaSourceList mediaSourceList;
+
+ @Before
+ public void setUp() {
+ mediaSourceList =
+ new MediaSourceList(mock(MediaSourceList.MediaSourceListInfoRefreshListener.class));
+ }
+
+ @Test
+ public void emptyMediaSourceList_expectConstantTimelineInstanceEMPTY() {
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
+ List<MediaSourceList.MediaSourceHolder> fakeHolders = createFakeHolders();
+
+ Timeline timeline = mediaSourceList.setMediaSources(fakeHolders, shuffleOrder);
+ assertNotSame(timeline, Timeline.EMPTY);
+
+ // Remove all media sources.
+ timeline =
+ mediaSourceList.removeMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ timeline.getWindowCount(), shuffleOrder);
+ assertSame(timeline, Timeline.EMPTY);
+
+ timeline = mediaSourceList.setMediaSources(fakeHolders, shuffleOrder);
+ assertNotSame(timeline, Timeline.EMPTY);
+ // Clear.
+ timeline = mediaSourceList.clear(shuffleOrder);
+ assertSame(timeline, Timeline.EMPTY);
+ }
+
+ @Test
+ public void prepareAndReprepareAfterRelease_expectSourcePreparationAfterMediaSourceListPrepare() {
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ mediaSourceList.setMediaSources(
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
+ // Verify prepare is called once on prepare.
+ verify(mockMediaSource1, times(0))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(0))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+ assertThat(mediaSourceList.isPrepared()).isTrue();
+ // Verify prepare is called once on prepare.
+ verify(mockMediaSource1, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+
+ mediaSourceList.release();
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+ // Verify prepare is called a second time on re-prepare.
+ verify(mockMediaSource1, times(2))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(2))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ }
+
+ @Test
+ public void setMediaSources_mediaSourceListUnprepared_notUsingLazyPreparation() {
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ List<MediaSourceList.MediaSourceHolder> mediaSources =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
+ Timeline timeline = mediaSourceList.setMediaSources(mediaSources, shuffleOrder);
+
+ assertThat(timeline.getWindowCount()).isEqualTo(2);
+ assertThat(mediaSourceList.getSize()).isEqualTo(2);
+
+ // Assert holder offsets have been set properly
+ for (int i = 0; i < mediaSources.size(); i++) {
+ MediaSourceList.MediaSourceHolder mediaSourceHolder = mediaSources.get(i);
+ assertThat(mediaSourceHolder.isRemoved).isFalse();
+ assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
+ }
+
+ // Set media items again. The second holder is re-used.
+ List<MediaSourceList.MediaSourceHolder> moreMediaSources =
+ createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class));
+ moreMediaSources.add(mediaSources.get(1));
+ timeline = mediaSourceList.setMediaSources(moreMediaSources, shuffleOrder);
+
+ assertThat(mediaSourceList.getSize()).isEqualTo(2);
+ assertThat(timeline.getWindowCount()).isEqualTo(2);
+ for (int i = 0; i < moreMediaSources.size(); i++) {
+ MediaSourceList.MediaSourceHolder mediaSourceHolder = moreMediaSources.get(i);
+ assertThat(mediaSourceHolder.isRemoved).isFalse();
+ assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
+ }
+ // Expect removed holders and sources to be removed without releasing.
+ verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSources.get(0).isRemoved).isTrue();
+ // Expect re-used holder and source not to be removed.
+ verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSources.get(1).isRemoved).isFalse();
+ }
+
+ @Test
+ public void setMediaSources_mediaSourceListPrepared_notUsingLazyPreparation() {
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ List<MediaSourceList.MediaSourceHolder> mediaSources =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
+
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+ mediaSourceList.setMediaSources(mediaSources, shuffleOrder);
+
+ // Verify sources are prepared.
+ verify(mockMediaSource1, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+
+ // Set media items again. The second holder is re-used.
+ List<MediaSourceList.MediaSourceHolder> moreMediaSources =
+ createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class));
+ moreMediaSources.add(mediaSources.get(1));
+ mediaSourceList.setMediaSources(moreMediaSources, shuffleOrder);
+
+ // Expect removed holders and sources to be removed and released.
+ verify(mockMediaSource1, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSources.get(0).isRemoved).isTrue();
+ // Expect re-used holder and source not to be removed but released.
+ verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSources.get(1).isRemoved).isFalse();
+ verify(mockMediaSource2, times(2))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ }
+
+ @Test
+ public void addMediaSources_mediaSourceListUnprepared_notUsingLazyPreparation_expectUnprepared() {
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ List<MediaSourceList.MediaSourceHolder> mediaSources =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
+ mediaSourceList.addMediaSources(
+ /* index= */ 0, mediaSources, new ShuffleOrder.DefaultShuffleOrder(2));
+
+ assertThat(mediaSourceList.getSize()).isEqualTo(2);
+ // Verify lazy initialization does not call prepare on sources.
+ verify(mockMediaSource1, times(0))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(0))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+
+ for (int i = 0; i < mediaSources.size(); i++) {
+ assertThat(mediaSources.get(i).firstWindowIndexInChild).isEqualTo(i);
+ assertThat(mediaSources.get(i).isRemoved).isFalse();
+ }
+
+ // Add for more sources in between.
+ List<MediaSourceList.MediaSourceHolder> moreMediaSources = createFakeHolders();
+ mediaSourceList.addMediaSources(
+ /* index= */ 1, moreMediaSources, new ShuffleOrder.DefaultShuffleOrder(/* length= */ 3));
+
+ assertThat(mediaSources.get(0).firstWindowIndexInChild).isEqualTo(0);
+ assertThat(moreMediaSources.get(0).firstWindowIndexInChild).isEqualTo(1);
+ assertThat(moreMediaSources.get(3).firstWindowIndexInChild).isEqualTo(4);
+ assertThat(mediaSources.get(1).firstWindowIndexInChild).isEqualTo(5);
+ }
+
+ @Test
+ public void addMediaSources_mediaSourceListPrepared_notUsingLazyPreparation_expectPrepared() {
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+ mediaSourceList.addMediaSources(
+ /* index= */ 0,
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
+
+ // Verify prepare is called on sources when added.
+ verify(mockMediaSource1, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ verify(mockMediaSource2, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ }
+
+ @Test
+ public void moveMediaSources() {
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
+ List<MediaSourceList.MediaSourceHolder> holders = createFakeHolders();
+ mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
+
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+ mediaSourceList.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 3, shuffleOrder);
+ assertFirstWindowInChildIndices(holders, 3, 0, 1, 2);
+ mediaSourceList.moveMediaSource(/* currentIndex= */ 3, /* newIndex= */ 0, shuffleOrder);
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
+ assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 2, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
+ assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 2, /* toIndex= */ 3, /* newFromIndex= */ 0, shuffleOrder);
+ assertFirstWindowInChildIndices(holders, 0, 3, 1, 2);
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 3, /* toIndex= */ 4, /* newFromIndex= */ 1, shuffleOrder);
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+
+ // No-ops.
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ 0, /* newFromIndex= */ 3, shuffleOrder);
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+ }
+
+ @Test
+ public void removeMediaSources_whenUnprepared_expectNoRelease() {
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ MediaSource mockMediaSource3 = mock(MediaSource.class);
+ MediaSource mockMediaSource4 = mock(MediaSource.class);
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
+
+ List<MediaSourceList.MediaSourceHolder> holders =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false,
+ mockMediaSource1,
+ mockMediaSource2,
+ mockMediaSource3,
+ mockMediaSource4);
+ mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
+ mediaSourceList.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
+
+ assertThat(mediaSourceList.getSize()).isEqualTo(2);
+ MediaSourceList.MediaSourceHolder removedHolder1 = holders.remove(1);
+ MediaSourceList.MediaSourceHolder removedHolder2 = holders.remove(1);
+
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+ assertThat(removedHolder1.isRemoved).isTrue();
+ assertThat(removedHolder2.isRemoved).isTrue();
+ verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource3, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ }
+
+ @Test
+ public void removeMediaSources_whenPrepared_expectRelease() {
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ MediaSource mockMediaSource3 = mock(MediaSource.class);
+ MediaSource mockMediaSource4 = mock(MediaSource.class);
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
+
+ List<MediaSourceList.MediaSourceHolder> holders =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false,
+ mockMediaSource1,
+ mockMediaSource2,
+ mockMediaSource3,
+ mockMediaSource4);
+ mediaSourceList.prepare(/* mediaTransferListener */ null);
+ mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
+ mediaSourceList.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
+
+ assertThat(mediaSourceList.getSize()).isEqualTo(2);
+ holders.remove(2);
+ holders.remove(1);
+
+ assertDefaultFirstWindowInChildIndexOrder(holders);
+ verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource3, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ }
+
+ @Test
+ public void release_mediaSourceListUnprepared_expectSourcesNotReleased() {
+ MediaSource mockMediaSource = mock(MediaSource.class);
+ MediaSourceList.MediaSourceHolder mediaSourceHolder =
+ new MediaSourceList.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
+
+ mediaSourceList.setMediaSources(
+ Collections.singletonList(mediaSourceHolder),
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
+ verify(mockMediaSource, times(0))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ mediaSourceList.release();
+ verify(mockMediaSource, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSourceHolder.isRemoved).isFalse();
+ }
+
+ @Test
+ public void release_mediaSourceListPrepared_expectSourcesReleasedNotRemoved() {
+ MediaSource mockMediaSource = mock(MediaSource.class);
+ MediaSourceList.MediaSourceHolder mediaSourceHolder =
+ new MediaSourceList.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
+
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+ mediaSourceList.setMediaSources(
+ Collections.singletonList(mediaSourceHolder),
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
+ verify(mockMediaSource, times(1))
+ .prepareSource(
+ any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
+ mediaSourceList.release();
+ verify(mockMediaSource, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
+ assertThat(mediaSourceHolder.isRemoved).isFalse();
+ }
+
+ @Test
+ public void clearMediaSourceList_expectSourcesReleasedAndRemoved() {
+ ShuffleOrder.DefaultShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
+ MediaSource mockMediaSource1 = mock(MediaSource.class);
+ MediaSource mockMediaSource2 = mock(MediaSource.class);
+ List<MediaSourceList.MediaSourceHolder> holders =
+ createFakeHoldersWithSources(
+ /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
+ mediaSourceList.setMediaSources(holders, shuffleOrder);
+ mediaSourceList.prepare(/* mediaTransferListener= */ null);
+
+ Timeline timeline = mediaSourceList.clear(shuffleOrder);
+ assertThat(timeline.isEmpty()).isTrue();
+ assertThat(holders.get(0).isRemoved).isTrue();
+ assertThat(holders.get(1).isRemoved).isTrue();
+ verify(mockMediaSource1, times(1)).releaseSource(any());
+ verify(mockMediaSource2, times(1)).releaseSource(any());
+ }
+
+ @Test
+ public void setMediaSources_expectTimelineUsesCustomShuffleOrder() {
+ Timeline timeline =
+ mediaSourceList.setMediaSources(createFakeHolders(), new FakeShuffleOrder(/* length=*/ 4));
+ assertTimelineUsesFakeShuffleOrder(timeline);
+ }
+
+ @Test
+ public void addMediaSources_expectTimelineUsesCustomShuffleOrder() {
+ Timeline timeline =
+ mediaSourceList.addMediaSources(
+ /* index= */ 0, createFakeHolders(), new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
+ assertTimelineUsesFakeShuffleOrder(timeline);
+ }
+
+ @Test
+ public void moveMediaSources_expectTimelineUsesCustomShuffleOrder() {
+ ShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
+ mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
+ Timeline timeline =
+ mediaSourceList.moveMediaSource(
+ /* currentIndex= */ 0, /* newIndex= */ 1, new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
+ assertTimelineUsesFakeShuffleOrder(timeline);
+ }
+
+ @Test
+ public void moveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
+ ShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
+ mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
+ Timeline timeline =
+ mediaSourceList.moveMediaSourceRange(
+ /* fromIndex= */ 0,
+ /* toIndex= */ 2,
+ /* newFromIndex= */ 2,
+ new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
+ assertTimelineUsesFakeShuffleOrder(timeline);
+ }
+
+ @Test
+ public void removeMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
+ ShuffleOrder shuffleOrder =
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
+ mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
+ Timeline timeline =
+ mediaSourceList.removeMediaSourceRange(
+ /* fromIndex= */ 0, /* toIndex= */ 2, new FakeShuffleOrder(/* length= */ 2));
+ assertTimelineUsesFakeShuffleOrder(timeline);
+ }
+
+ @Test
+ public void setShuffleOrder_expectTimelineUsesCustomShuffleOrder() {
+ mediaSourceList.setMediaSources(
+ createFakeHolders(),
+ new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE));
+ assertTimelineUsesFakeShuffleOrder(
+ mediaSourceList.setShuffleOrder(new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE)));
+ }
+
+ // Internal methods.
+
+ private static void assertTimelineUsesFakeShuffleOrder(Timeline timeline) {
+ assertThat(
+ timeline.getNextWindowIndex(
+ /* windowIndex= */ 0, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
+ .isEqualTo(-1);
+ assertThat(
+ timeline.getPreviousWindowIndex(
+ /* windowIndex= */ timeline.getWindowCount() - 1,
+ Player.REPEAT_MODE_OFF,
+ /* shuffleModeEnabled= */ true))
+ .isEqualTo(-1);
+ }
+
+ private static void assertDefaultFirstWindowInChildIndexOrder(
+ List<MediaSourceList.MediaSourceHolder> holders) {
+ int[] indices = new int[holders.size()];
+ for (int i = 0; i < indices.length; i++) {
+ indices[i] = i;
+ }
+ assertFirstWindowInChildIndices(holders, indices);
+ }
+
+ private static void assertFirstWindowInChildIndices(
+ List<MediaSourceList.MediaSourceHolder> holders, int... firstWindowInChildIndices) {
+ assertThat(holders).hasSize(firstWindowInChildIndices.length);
+ for (int i = 0; i < holders.size(); i++) {
+ assertThat(holders.get(i).firstWindowIndexInChild).isEqualTo(firstWindowInChildIndices[i]);
+ }
+ }
+
+ private static List<MediaSourceList.MediaSourceHolder> createFakeHolders() {
+ MediaSource fakeMediaSource = new FakeMediaSource(new FakeTimeline(1));
+ List<MediaSourceList.MediaSourceHolder> holders = new ArrayList<>();
+ for (int i = 0; i < MEDIA_SOURCE_LIST_SIZE; i++) {
+ holders.add(
+ new MediaSourceList.MediaSourceHolder(fakeMediaSource, /* useLazyPreparation= */ true));
+ }
+ return holders;
+ }
+
+ private static List<MediaSourceList.MediaSourceHolder> createFakeHoldersWithSources(
+ boolean useLazyPreparation, MediaSource... sources) {
+ List<MediaSourceList.MediaSourceHolder> holders = new ArrayList<>();
+ for (MediaSource mediaSource : sources) {
+ holders.add(
+ new MediaSourceList.MediaSourceHolder(
+ mediaSource, /* useLazyPreparation= */ useLazyPreparation));
+ }
+ return holders;
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/PlayerMessageTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/PlayerMessageTest.java
index a7ef451..874a8c5 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/PlayerMessageTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/PlayerMessageTest.java
@@ -53,7 +53,7 @@
initMocks(this);
PlayerMessage.Sender sender = (message) -> {};
PlayerMessage.Target target = (messageType, payload) -> {};
- handlerThread = new HandlerThread("TestHandlerThread");
+ handlerThread = new HandlerThread("TestHandler");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
message =
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java
deleted file mode 100644
index cc551db..0000000
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.testutil.FakeMediaSource;
-import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
-import com.google.android.exoplayer2.testutil.FakeTimeline;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit test for {@link Playlist}. */
-@RunWith(AndroidJUnit4.class)
-public class PlaylistTest {
-
- private static final int PLAYLIST_SIZE = 4;
-
- private Playlist playlist;
-
- @Before
- public void setUp() {
- playlist = new Playlist(mock(Playlist.PlaylistInfoRefreshListener.class));
- }
-
- @Test
- public void testEmptyPlaylist_expectConstantTimelineInstanceEMPTY() {
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
- List<Playlist.MediaSourceHolder> fakeHolders = createFakeHolders();
-
- Timeline timeline = playlist.setMediaSources(fakeHolders, shuffleOrder);
- assertNotSame(timeline, Timeline.EMPTY);
-
- // Remove all media sources.
- timeline =
- playlist.removeMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ timeline.getWindowCount(), shuffleOrder);
- assertSame(timeline, Timeline.EMPTY);
-
- timeline = playlist.setMediaSources(fakeHolders, shuffleOrder);
- assertNotSame(timeline, Timeline.EMPTY);
- // Clear.
- timeline = playlist.clear(shuffleOrder);
- assertSame(timeline, Timeline.EMPTY);
- }
-
- @Test
- public void testPrepareAndReprepareAfterRelease_expectSourcePreparationAfterPlaylistPrepare() {
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- playlist.setMediaSources(
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
- // Verify prepare is called once on prepare.
- verify(mockMediaSource1, times(0))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(0))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
-
- playlist.prepare(/* mediaTransferListener= */ null);
- assertThat(playlist.isPrepared()).isTrue();
- // Verify prepare is called once on prepare.
- verify(mockMediaSource1, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
-
- playlist.release();
- playlist.prepare(/* mediaTransferListener= */ null);
- // Verify prepare is called a second time on re-prepare.
- verify(mockMediaSource1, times(2))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(2))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- }
-
- @Test
- public void testSetMediaSources_playlistUnprepared_notUsingLazyPreparation() {
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- List<Playlist.MediaSourceHolder> mediaSources =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
- Timeline timeline = playlist.setMediaSources(mediaSources, shuffleOrder);
-
- assertThat(timeline.getWindowCount()).isEqualTo(2);
- assertThat(playlist.getSize()).isEqualTo(2);
-
- // Assert holder offsets have been set properly
- for (int i = 0; i < mediaSources.size(); i++) {
- Playlist.MediaSourceHolder mediaSourceHolder = mediaSources.get(i);
- assertThat(mediaSourceHolder.isRemoved).isFalse();
- assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
- }
-
- // Set media items again. The second holder is re-used.
- List<Playlist.MediaSourceHolder> moreMediaSources =
- createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class));
- moreMediaSources.add(mediaSources.get(1));
- timeline = playlist.setMediaSources(moreMediaSources, shuffleOrder);
-
- assertThat(playlist.getSize()).isEqualTo(2);
- assertThat(timeline.getWindowCount()).isEqualTo(2);
- for (int i = 0; i < moreMediaSources.size(); i++) {
- Playlist.MediaSourceHolder mediaSourceHolder = moreMediaSources.get(i);
- assertThat(mediaSourceHolder.isRemoved).isFalse();
- assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
- }
- // Expect removed holders and sources to be removed without releasing.
- verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSources.get(0).isRemoved).isTrue();
- // Expect re-used holder and source not to be removed.
- verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSources.get(1).isRemoved).isFalse();
- }
-
- @Test
- public void testSetMediaSources_playlistPrepared_notUsingLazyPreparation() {
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- List<Playlist.MediaSourceHolder> mediaSources =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
-
- playlist.prepare(/* mediaTransferListener= */ null);
- playlist.setMediaSources(mediaSources, shuffleOrder);
-
- // Verify sources are prepared.
- verify(mockMediaSource1, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
-
- // Set media items again. The second holder is re-used.
- List<Playlist.MediaSourceHolder> moreMediaSources =
- createFakeHoldersWithSources(/* useLazyPreparation= */ false, mock(MediaSource.class));
- moreMediaSources.add(mediaSources.get(1));
- playlist.setMediaSources(moreMediaSources, shuffleOrder);
-
- // Expect removed holders and sources to be removed and released.
- verify(mockMediaSource1, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSources.get(0).isRemoved).isTrue();
- // Expect re-used holder and source not to be removed but released.
- verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSources.get(1).isRemoved).isFalse();
- verify(mockMediaSource2, times(2))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- }
-
- @Test
- public void testAddMediaSources_playlistUnprepared_notUsingLazyPreparation_expectUnprepared() {
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- List<Playlist.MediaSourceHolder> mediaSources =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
- playlist.addMediaSources(/* index= */ 0, mediaSources, new ShuffleOrder.DefaultShuffleOrder(2));
-
- assertThat(playlist.getSize()).isEqualTo(2);
- // Verify lazy initialization does not call prepare on sources.
- verify(mockMediaSource1, times(0))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(0))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
-
- for (int i = 0; i < mediaSources.size(); i++) {
- assertThat(mediaSources.get(i).firstWindowIndexInChild).isEqualTo(i);
- assertThat(mediaSources.get(i).isRemoved).isFalse();
- }
-
- // Add for more sources in between.
- List<Playlist.MediaSourceHolder> moreMediaSources = createFakeHolders();
- playlist.addMediaSources(
- /* index= */ 1, moreMediaSources, new ShuffleOrder.DefaultShuffleOrder(/* length= */ 3));
-
- assertThat(mediaSources.get(0).firstWindowIndexInChild).isEqualTo(0);
- assertThat(moreMediaSources.get(0).firstWindowIndexInChild).isEqualTo(1);
- assertThat(moreMediaSources.get(3).firstWindowIndexInChild).isEqualTo(4);
- assertThat(mediaSources.get(1).firstWindowIndexInChild).isEqualTo(5);
- }
-
- @Test
- public void testAddMediaSources_playlistPrepared_notUsingLazyPreparation_expectPrepared() {
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- playlist.prepare(/* mediaTransferListener= */ null);
- playlist.addMediaSources(
- /* index= */ 0,
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
-
- // Verify prepare is called on sources when added.
- verify(mockMediaSource1, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- verify(mockMediaSource2, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- }
-
- @Test
- public void testMoveMediaSources() {
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
- List<Playlist.MediaSourceHolder> holders = createFakeHolders();
- playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder);
-
- assertDefaultFirstWindowInChildIndexOrder(holders);
- playlist.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 3, shuffleOrder);
- assertFirstWindowInChildIndices(holders, 3, 0, 1, 2);
- playlist.moveMediaSource(/* currentIndex= */ 3, /* newIndex= */ 0, shuffleOrder);
- assertDefaultFirstWindowInChildIndexOrder(holders);
-
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
- assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 2, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
- assertDefaultFirstWindowInChildIndexOrder(holders);
-
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
- assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 2, /* toIndex= */ 3, /* newFromIndex= */ 0, shuffleOrder);
- assertFirstWindowInChildIndices(holders, 0, 3, 1, 2);
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 3, /* toIndex= */ 4, /* newFromIndex= */ 1, shuffleOrder);
- assertDefaultFirstWindowInChildIndexOrder(holders);
-
- // No-ops.
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
- assertDefaultFirstWindowInChildIndexOrder(holders);
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ 0, /* newFromIndex= */ 3, shuffleOrder);
- assertDefaultFirstWindowInChildIndexOrder(holders);
- }
-
- @Test
- public void testRemoveMediaSources_whenUnprepared_expectNoRelease() {
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- MediaSource mockMediaSource3 = mock(MediaSource.class);
- MediaSource mockMediaSource4 = mock(MediaSource.class);
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
-
- List<Playlist.MediaSourceHolder> holders =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false,
- mockMediaSource1,
- mockMediaSource2,
- mockMediaSource3,
- mockMediaSource4);
- playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder);
- playlist.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
-
- assertThat(playlist.getSize()).isEqualTo(2);
- Playlist.MediaSourceHolder removedHolder1 = holders.remove(1);
- Playlist.MediaSourceHolder removedHolder2 = holders.remove(1);
-
- assertDefaultFirstWindowInChildIndexOrder(holders);
- assertThat(removedHolder1.isRemoved).isTrue();
- assertThat(removedHolder2.isRemoved).isTrue();
- verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource3, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- }
-
- @Test
- public void testRemoveMediaSources_whenPrepared_expectRelease() {
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- MediaSource mockMediaSource3 = mock(MediaSource.class);
- MediaSource mockMediaSource4 = mock(MediaSource.class);
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
-
- List<Playlist.MediaSourceHolder> holders =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false,
- mockMediaSource1,
- mockMediaSource2,
- mockMediaSource3,
- mockMediaSource4);
- playlist.prepare(/* mediaTransferListener */ null);
- playlist.addMediaSources(/* index= */ 0, holders, shuffleOrder);
- playlist.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
-
- assertThat(playlist.getSize()).isEqualTo(2);
- holders.remove(2);
- holders.remove(1);
-
- assertDefaultFirstWindowInChildIndexOrder(holders);
- verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource3, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- }
-
- @Test
- public void testRelease_playlistUnprepared_expectSourcesNotReleased() {
- MediaSource mockMediaSource = mock(MediaSource.class);
- Playlist.MediaSourceHolder mediaSourceHolder =
- new Playlist.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
-
- playlist.setMediaSources(
- Collections.singletonList(mediaSourceHolder),
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
- verify(mockMediaSource, times(0))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- playlist.release();
- verify(mockMediaSource, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSourceHolder.isRemoved).isFalse();
- }
-
- @Test
- public void testRelease_playlistPrepared_expectSourcesReleasedNotRemoved() {
- MediaSource mockMediaSource = mock(MediaSource.class);
- Playlist.MediaSourceHolder mediaSourceHolder =
- new Playlist.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
-
- playlist.prepare(/* mediaTransferListener= */ null);
- playlist.setMediaSources(
- Collections.singletonList(mediaSourceHolder),
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
- verify(mockMediaSource, times(1))
- .prepareSource(
- any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull());
- playlist.release();
- verify(mockMediaSource, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
- assertThat(mediaSourceHolder.isRemoved).isFalse();
- }
-
- @Test
- public void testClearPlaylist_expectSourcesReleasedAndRemoved() {
- ShuffleOrder.DefaultShuffleOrder shuffleOrder =
- new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
- MediaSource mockMediaSource1 = mock(MediaSource.class);
- MediaSource mockMediaSource2 = mock(MediaSource.class);
- List<Playlist.MediaSourceHolder> holders =
- createFakeHoldersWithSources(
- /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
- playlist.setMediaSources(holders, shuffleOrder);
- playlist.prepare(/* mediaTransferListener= */ null);
-
- Timeline timeline = playlist.clear(shuffleOrder);
- assertThat(timeline.isEmpty()).isTrue();
- assertThat(holders.get(0).isRemoved).isTrue();
- assertThat(holders.get(1).isRemoved).isTrue();
- verify(mockMediaSource1, times(1)).releaseSource(any());
- verify(mockMediaSource2, times(1)).releaseSource(any());
- }
-
- @Test
- public void testSetMediaSources_expectTimelineUsesCustomShuffleOrder() {
- Timeline timeline =
- playlist.setMediaSources(createFakeHolders(), new FakeShuffleOrder(/* length=*/ 4));
- assertTimelineUsesFakeShuffleOrder(timeline);
- }
-
- @Test
- public void testAddMediaSources_expectTimelineUsesCustomShuffleOrder() {
- Timeline timeline =
- playlist.addMediaSources(
- /* index= */ 0, createFakeHolders(), new FakeShuffleOrder(PLAYLIST_SIZE));
- assertTimelineUsesFakeShuffleOrder(timeline);
- }
-
- @Test
- public void testMoveMediaSources_expectTimelineUsesCustomShuffleOrder() {
- ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE);
- playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
- Timeline timeline =
- playlist.moveMediaSource(
- /* currentIndex= */ 0, /* newIndex= */ 1, new FakeShuffleOrder(PLAYLIST_SIZE));
- assertTimelineUsesFakeShuffleOrder(timeline);
- }
-
- @Test
- public void testMoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
- ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE);
- playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
- Timeline timeline =
- playlist.moveMediaSourceRange(
- /* fromIndex= */ 0,
- /* toIndex= */ 2,
- /* newFromIndex= */ 2,
- new FakeShuffleOrder(PLAYLIST_SIZE));
- assertTimelineUsesFakeShuffleOrder(timeline);
- }
-
- @Test
- public void testRemoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
- ShuffleOrder shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE);
- playlist.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
- Timeline timeline =
- playlist.removeMediaSourceRange(
- /* fromIndex= */ 0, /* toIndex= */ 2, new FakeShuffleOrder(/* length= */ 2));
- assertTimelineUsesFakeShuffleOrder(timeline);
- }
-
- @Test
- public void testSetShuffleOrder_expectTimelineUsesCustomShuffleOrder() {
- playlist.setMediaSources(
- createFakeHolders(), new ShuffleOrder.DefaultShuffleOrder(/* length= */ PLAYLIST_SIZE));
- assertTimelineUsesFakeShuffleOrder(
- playlist.setShuffleOrder(new FakeShuffleOrder(PLAYLIST_SIZE)));
- }
-
- // Internal methods.
-
- private static void assertTimelineUsesFakeShuffleOrder(Timeline timeline) {
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 0, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
- .isEqualTo(-1);
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ timeline.getWindowCount() - 1,
- Player.REPEAT_MODE_OFF,
- /* shuffleModeEnabled= */ true))
- .isEqualTo(-1);
- }
-
- private static void assertDefaultFirstWindowInChildIndexOrder(
- List<Playlist.MediaSourceHolder> holders) {
- int[] indices = new int[holders.size()];
- for (int i = 0; i < indices.length; i++) {
- indices[i] = i;
- }
- assertFirstWindowInChildIndices(holders, indices);
- }
-
- private static void assertFirstWindowInChildIndices(
- List<Playlist.MediaSourceHolder> holders, int... firstWindowInChildIndices) {
- assertThat(holders).hasSize(firstWindowInChildIndices.length);
- for (int i = 0; i < holders.size(); i++) {
- assertThat(holders.get(i).firstWindowIndexInChild).isEqualTo(firstWindowInChildIndices[i]);
- }
- }
-
- private static List<Playlist.MediaSourceHolder> createFakeHolders() {
- MediaSource fakeMediaSource = new FakeMediaSource(new FakeTimeline(1));
- List<Playlist.MediaSourceHolder> holders = new ArrayList<>();
- for (int i = 0; i < PLAYLIST_SIZE; i++) {
- holders.add(new Playlist.MediaSourceHolder(fakeMediaSource, /* useLazyPreparation= */ true));
- }
- return holders;
- }
-
- private static List<Playlist.MediaSourceHolder> createFakeHoldersWithSources(
- boolean useLazyPreparation, MediaSource... sources) {
- List<Playlist.MediaSourceHolder> holders = new ArrayList<>();
- for (MediaSource mediaSource : sources) {
- holders.add(
- new Playlist.MediaSourceHolder(
- mediaSource, /* useLazyPreparation= */ useLazyPreparation));
- }
- return holders;
- }
-}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java
index 6bc70e3..9a28f0b 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java
@@ -29,12 +29,12 @@
public class TimelineTest {
@Test
- public void testEmptyTimeline() {
+ public void emptyTimeline() {
TimelineAsserts.assertEmpty(Timeline.EMPTY);
}
@Test
- public void testSinglePeriodTimeline() {
+ public void singlePeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111));
TimelineAsserts.assertWindowTags(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 1);
@@ -48,7 +48,7 @@
}
@Test
- public void testMultiPeriodTimeline() {
+ public void multiPeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111));
TimelineAsserts.assertWindowTags(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 5);
@@ -62,7 +62,7 @@
}
@Test
- public void testWindowEquals() {
+ public void windowEquals() {
Timeline.Window window = new Timeline.Window();
assertThat(window).isEqualTo(new Timeline.Window());
@@ -95,6 +95,10 @@
assertThat(window).isNotEqualTo(otherWindow);
otherWindow = new Timeline.Window();
+ otherWindow.isPlaceholder = true;
+ assertThat(window).isNotEqualTo(otherWindow);
+
+ otherWindow = new Timeline.Window();
otherWindow.defaultPositionUs = C.TIME_UNSET;
assertThat(window).isNotEqualTo(otherWindow);
@@ -147,7 +151,7 @@
}
@Test
- public void testWindowHashCode() {
+ public void windowHashCode() {
Timeline.Window window = new Timeline.Window();
Timeline.Window otherWindow = new Timeline.Window();
assertThat(window.hashCode()).isEqualTo(otherWindow.hashCode());
@@ -159,7 +163,7 @@
}
@Test
- public void testPeriodEquals() {
+ public void periodEquals() {
Timeline.Period period = new Timeline.Period();
assertThat(period).isEqualTo(new Timeline.Period());
@@ -195,7 +199,7 @@
}
@Test
- public void testPeriodHashCode() {
+ public void periodHashCode() {
Timeline.Period period = new Timeline.Period();
Timeline.Period otherPeriod = new Timeline.Period();
assertThat(period.hashCode()).isEqualTo(otherPeriod.hashCode());
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/WakeLockManagerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/WakeLockManagerTest.java
new file mode 100644
index 0000000..da9fedd
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/WakeLockManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.PowerManager.WakeLock;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.shadows.ShadowPowerManager;
+
+/** Unit tests for {@link WakeLockManager} */
+@RunWith(AndroidJUnit4.class)
+public class WakeLockManagerTest {
+
+ private Context context;
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void stayAwakeFalse_wakeLockIsNeverHeld() {
+ WakeLockManager wakeLockManager = new WakeLockManager(context);
+ wakeLockManager.setEnabled(true);
+ wakeLockManager.setStayAwake(false);
+
+ WakeLock wakeLock = ShadowPowerManager.getLatestWakeLock();
+ assertThat(wakeLock.isHeld()).isFalse();
+
+ wakeLockManager.setEnabled(false);
+
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+
+ @Test
+ public void stayAwakeTrue_wakeLockIsOnlyHeldWhenEnabled() {
+ WakeLockManager wakeLockManager = new WakeLockManager(context);
+ wakeLockManager.setEnabled(true);
+ wakeLockManager.setStayAwake(true);
+
+ WakeLock wakeLock = ShadowPowerManager.getLatestWakeLock();
+
+ assertThat(wakeLock.isHeld()).isTrue();
+
+ wakeLockManager.setEnabled(false);
+
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
index ced210d..1d22984 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
@@ -17,8 +17,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.os.Handler;
-import android.os.SystemClock;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
@@ -26,14 +24,12 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Window;
-import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
@@ -46,13 +42,13 @@
import com.google.android.exoplayer2.testutil.ActionSchedule;
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
+import com.google.android.exoplayer2.testutil.FakeAudioRenderer;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
-import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
+import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Util;
-import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
@@ -69,12 +65,14 @@
@LooperMode(Mode.PAUSED)
public final class AnalyticsCollectorTest {
+ private static final String TAG = "AnalyticsCollectorTest";
+
private static final int EVENT_PLAYER_STATE_CHANGED = 0;
private static final int EVENT_TIMELINE_CHANGED = 1;
private static final int EVENT_POSITION_DISCONTINUITY = 2;
private static final int EVENT_SEEK_STARTED = 3;
private static final int EVENT_SEEK_PROCESSED = 4;
- private static final int EVENT_PLAYBACK_PARAMETERS_CHANGED = 5;
+ private static final int EVENT_PLAYBACK_SPEED_CHANGED = 5;
private static final int EVENT_REPEAT_MODE_CHANGED = 6;
private static final int EVENT_SHUFFLE_MODE_CHANGED = 7;
private static final int EVENT_LOADING_CHANGED = 8;
@@ -107,6 +105,7 @@
private static final int EVENT_DRM_KEYS_REMOVED = 36;
private static final int EVENT_DRM_SESSION_ACQUIRED = 37;
private static final int EVENT_DRM_SESSION_RELEASED = 38;
+ private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 39;
private static final int TIMEOUT_MS = 10000;
private static final Timeline SINGLE_PERIOD_TIMELINE = new FakeTimeline(/* windowCount= */ 1);
@@ -126,12 +125,10 @@
private EventWindowAndPeriodId window1Period0Seq1;
@Test
- public void testEmptyTimeline() throws Exception {
+ public void emptyTimeline() throws Exception {
FakeMediaSource mediaSource =
new FakeMediaSource(
- Timeline.EMPTY,
- ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
- ExoPlayerTestRunner.Builder.AUDIO_FORMAT);
+ Timeline.EMPTY, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT);
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED))
@@ -143,12 +140,12 @@
}
@Test
- public void testSinglePeriod() throws Exception {
+ public void singlePeriod() throws Exception {
FakeMediaSource mediaSource =
new FakeMediaSource(
SINGLE_PERIOD_TIMELINE,
- ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
- ExoPlayerTestRunner.Builder.AUDIO_FORMAT);
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT);
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
populateEventIds(listener.lastReportedTimeline);
@@ -181,21 +178,22 @@
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
listener.assertNoMoreEvents();
}
@Test
- public void testAutomaticPeriodTransition() throws Exception {
+ public void automaticPeriodTransition() throws Exception {
MediaSource mediaSource =
new ConcatenatingMediaSource(
new FakeMediaSource(
SINGLE_PERIOD_TIMELINE,
- ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
- ExoPlayerTestRunner.Builder.AUDIO_FORMAT),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
SINGLE_PERIOD_TIMELINE,
- ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
- ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT));
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
populateEventIds(listener.lastReportedTimeline);
@@ -239,17 +237,18 @@
period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1);
- assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
- assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
+ assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period1);
+ assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0, period1);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period1);
listener.assertNoMoreEvents();
}
@Test
- public void testPeriodTransitionWithRendererChange() throws Exception {
+ public void periodTransitionWithRendererChange() throws Exception {
MediaSource mediaSource =
new ConcatenatingMediaSource(
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT),
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT));
TestAnalyticsListener listener = runAnalyticsTest(mediaSource);
populateEventIds(listener.lastReportedTimeline);
@@ -258,8 +257,6 @@
WINDOW_0 /* setPlayWhenReady */,
WINDOW_0 /* BUFFERING */,
period0 /* READY */,
- period1 /* BUFFERING */,
- period1 /* READY */,
period1 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */);
@@ -295,19 +292,27 @@
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
listener.assertNoMoreEvents();
}
@Test
- public void testSeekToOtherPeriod() throws Exception {
+ public void seekToOtherPeriod() throws Exception {
MediaSource mediaSource =
new ConcatenatingMediaSource(
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT),
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
+ new FakeMediaSource(
+ SINGLE_PERIOD_TIMELINE,
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.AUDIO_FORMAT));
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
- .waitForPlaybackState(Player.STATE_READY)
+ // Wait until second period has fully loaded to assert loading events without flakiness.
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
.seek(/* windowIndex= */ 1, /* positionMs= */ 0)
.play()
.build();
@@ -321,8 +326,8 @@
WINDOW_0 /* setPlayWhenReady=false */,
period0 /* READY */,
period1 /* BUFFERING */,
- period1 /* READY */,
period1 /* setPlayWhenReady=true */,
+ period1 /* READY */,
period1 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */);
@@ -346,36 +351,37 @@
WINDOW_1 /* manifest */,
period1 /* media */);
assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED))
- .containsExactly(period0 /* video */, period1 /* audio */);
+ .containsExactly(period0 /* video */, period0 /* audio */, period1 /* audio */);
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)).containsExactly(period0, period1);
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0, period1);
assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
- .containsExactly(period0 /* video */, period1 /* audio */);
+ .containsExactly(period0 /* video */, period0 /* audio */, period1 /* audio */);
assertThat(listener.getEvents(EVENT_DECODER_INIT))
- .containsExactly(period0 /* video */, period1 /* audio */);
+ .containsExactly(period0 /* video */, period0 /* audio */, period1 /* audio */);
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
- .containsExactly(period0 /* video */, period1 /* audio */);
- assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0);
- assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
+ .containsExactly(period0 /* video */, period0 /* audio */, period1 /* audio */);
+ assertThat(listener.getEvents(EVENT_DECODER_DISABLED))
+ .containsExactly(period0 /* video */, period0 /* audio */);
+ assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0, period1);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
listener.assertNoMoreEvents();
}
@Test
- public void testSeekBackAfterReadingAhead() throws Exception {
+ public void seekBackAfterReadingAhead() throws Exception {
MediaSource mediaSource =
new ConcatenatingMediaSource(
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT),
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
SINGLE_PERIOD_TIMELINE,
- ExoPlayerTestRunner.Builder.VIDEO_FORMAT,
- ExoPlayerTestRunner.Builder.AUDIO_FORMAT));
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT));
long periodDurationMs =
SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, periodDurationMs)
@@ -396,8 +402,6 @@
period0 /* BUFFERING */,
period0 /* READY */,
period0 /* setPlayWhenReady=true */,
- period1Seq2 /* BUFFERING */,
- period1Seq2 /* READY */,
period1Seq2 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */);
@@ -423,7 +427,7 @@
period1Seq1 /* media */,
period1Seq2 /* media */);
assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED))
- .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
+ .containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED))
.containsExactly(period0, period1Seq1, period1Seq2);
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED))
@@ -431,31 +435,39 @@
assertThat(listener.getEvents(EVENT_READING_STARTED))
.containsExactly(period0, period1Seq1, period1Seq2);
assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
- .containsExactly(period0, period0, period1Seq2);
+ .containsExactly(period0, period1, period0, period1Seq2);
assertThat(listener.getEvents(EVENT_DECODER_INIT))
- .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
+ .containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
- .containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
- assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0);
- assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2);
+ .containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
+ assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0, period0);
+ assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
+ .containsExactly(period1Seq1, period1Seq2);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
- .containsExactly(period0, period1Seq2, period1Seq2);
- assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period0);
- assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0, period0);
+ .containsExactly(period0, period1Seq2);
+ assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
+ .containsExactly(period0, period1Seq1, period0, period1Seq2);
+ assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
+ .containsExactly(period0, period1Seq1, period0, period1Seq2);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(period0, period1Seq2);
listener.assertNoMoreEvents();
}
@Test
- public void testPrepareNewSource() throws Exception {
+ public void prepareNewSource() throws Exception {
MediaSource mediaSource1 =
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
MediaSource mediaSource2 =
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.setMediaSources(/* resetPosition= */ false, mediaSource2)
+ .waitForTimelineChanged()
+ // Wait until loading started to prevent flakiness caused by loading finishing too fast.
+ .waitForIsLoading(true)
.play()
.build();
TestAnalyticsListener listener = runAnalyticsTest(mediaSource1, actionSchedule);
@@ -476,7 +488,7 @@
WINDOW_0 /* setPlayWhenReady=false */,
period0Seq0 /* READY */,
WINDOW_0 /* BUFFERING */,
- WINDOW_0 /* setPlayWhenReady=true */,
+ period0Seq1 /* setPlayWhenReady=true */,
period0Seq1 /* READY */,
period0Seq1 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
@@ -518,21 +530,25 @@
.containsExactly(period0Seq0, period0Seq1);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(period0Seq0, period0Seq1);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(period0Seq1);
listener.assertNoMoreEvents();
}
@Test
- public void testReprepareAfterError() throws Exception {
+ public void reprepareAfterError() throws Exception {
MediaSource mediaSource =
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.throwPlaybackException(ExoPlaybackException.createForSource(new IOException()))
.waitForPlaybackState(Player.STATE_IDLE)
.seek(/* positionMs= */ 0)
.prepare()
+ // Wait until loading started to assert loading events without flakiness.
+ .waitForIsLoading(true)
.play()
.waitForPlaybackState(Player.STATE_ENDED)
.build();
@@ -587,19 +603,21 @@
.containsExactly(period0Seq0, period0Seq0);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(period0Seq0, period0Seq0);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(period0Seq0);
listener.assertNoMoreEvents();
}
@Test
- public void testDynamicTimelineChange() throws Exception {
+ public void dynamicTimelineChange() throws Exception {
MediaSource childMediaSource =
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
final ConcatenatingMediaSource concatenatedMediaSource =
new ConcatenatingMediaSource(childMediaSource, childMediaSource);
long periodDurationMs =
SINGLE_PERIOD_TIMELINE.getWindow(/* windowIndex= */ 0, new Window()).getDurationMs();
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
// Ensure second period is already being read from.
@@ -609,6 +627,7 @@
concatenatedMediaSource.moveMediaSource(
/* currentIndex= */ 0, /* newIndex= */ 1))
.waitForTimelineChanged()
+ .waitForPlaybackState(Player.STATE_READY)
.play()
.build();
TestAnalyticsListener listener = runAnalyticsTest(concatenatedMediaSource, actionSchedule);
@@ -624,6 +643,7 @@
window0Period1Seq0 /* setPlayWhenReady=false */,
period1Seq0 /* setPlayWhenReady=true */,
period1Seq0 /* BUFFERING */,
+ period1Seq0 /* READY */,
period1Seq0 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
.containsExactly(
@@ -658,18 +678,23 @@
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
.containsExactly(window0Period1Seq0, window1Period0Seq1);
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(window0Period1Seq0);
- assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(window0Period1Seq0);
- assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(window0Period1Seq0);
- assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(window0Period1Seq0);
+ assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
+ .containsExactly(window0Period1Seq0, period1Seq0);
+ assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
+ .containsExactly(window0Period1Seq0, window1Period0Seq1, period1Seq0);
+ assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
+ .containsExactly(window0Period1Seq0, window1Period0Seq1, period1Seq0);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(window0Period1Seq0, period1Seq0);
listener.assertNoMoreEvents();
}
@Test
- public void testPlaylistOperations() throws Exception {
+ public void playlistOperations() throws Exception {
MediaSource fakeMediaSource =
- new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.addMediaSources(fakeMediaSource)
@@ -677,6 +702,8 @@
.waitForIsLoading(true)
.waitForIsLoading(false)
.removeMediaItem(/* index= */ 0)
+ .waitForPlaybackState(Player.STATE_BUFFERING)
+ .waitForPlaybackState(Player.STATE_READY)
.play()
.build();
TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule);
@@ -698,8 +725,8 @@
WINDOW_0 /* BUFFERING */,
period0Seq0 /* READY */,
period0Seq1 /* BUFFERING */,
- period0Seq1 /* setPlayWhenReady=true */,
period0Seq1 /* READY */,
+ period0Seq1 /* setPlayWhenReady=true */,
period0Seq1 /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
.containsExactly(
@@ -721,30 +748,34 @@
.containsExactly(period0Seq0, period1Seq1);
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq0);
assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0Seq0, period0Seq1);
- assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(period0Seq0, period0Seq1);
+ assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
+ .containsExactly(period0Seq0, period0Seq1, period0Seq1);
assertThat(listener.getEvents(EVENT_DECODER_INIT)).containsExactly(period0Seq0, period0Seq1);
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
.containsExactly(period0Seq0, period0Seq1);
- assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0);
+ assertThat(listener.getEvents(EVENT_DECODER_DISABLED))
+ .containsExactly(period0Seq0, period0Seq0);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
.containsExactly(period0Seq0, period0Seq1);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(period0Seq0, period0Seq1);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(period0Seq1);
listener.assertNoMoreEvents();
}
@Test
- public void testAdPlayback() throws Exception {
+ public void adPlayback() throws Exception {
long contentDurationsUs = 10 * C.MICROS_PER_SECOND;
AtomicReference<AdPlaybackState> adPlaybackState =
new AtomicReference<>(
FakeTimeline.createAdPlaybackState(
- /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */
- 0,
- 5 * C.MICROS_PER_SECOND,
- C.TIME_END_OF_SOURCE)
- .withContentDurationUs(contentDurationsUs));
+ /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */
+ 0,
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
+ + 5 * C.MICROS_PER_SECOND,
+ C.TIME_END_OF_SOURCE));
AtomicInteger playedAdCount = new AtomicInteger(0);
Timeline adTimeline =
new FakeTimeline(
@@ -756,9 +787,9 @@
contentDurationsUs,
adPlaybackState.get()));
FakeMediaSource fakeMediaSource =
- new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(adTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
@@ -785,14 +816,26 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
- adPlaybackState.get())),
- /* newManifest= */ null);
+ adPlaybackState.get())));
}
}
});
}
})
.pause()
+ // Ensure everything is preloaded.
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
+ .waitForIsLoading(true)
+ .waitForIsLoading(false)
.waitForPlaybackState(Player.STATE_READY)
// Wait in each content part to ensure previously triggered events get a chance to be
// delivered. This prevents flakiness caused by playback progressing too fast.
@@ -947,13 +990,29 @@
contentAfterPostroll);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll);
- assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(prerollAd);
- assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(prerollAd);
+ assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
+ .containsExactly(
+ prerollAd,
+ contentAfterPreroll,
+ midrollAd,
+ contentAfterMidroll,
+ postrollAd,
+ contentAfterPostroll);
+ assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
+ .containsExactly(
+ prerollAd,
+ contentAfterPreroll,
+ midrollAd,
+ contentAfterMidroll,
+ postrollAd,
+ contentAfterPostroll);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll);
listener.assertNoMoreEvents();
}
@Test
- public void testSeekAfterMidroll() throws Exception {
+ public void seekAfterMidroll() throws Exception {
Timeline adTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
@@ -963,11 +1022,13 @@
/* isDynamic= */ false,
10 * C.MICROS_PER_SECOND,
FakeTimeline.createAdPlaybackState(
- /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ 5 * C.MICROS_PER_SECOND)));
+ /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
+ + 5 * C.MICROS_PER_SECOND)));
FakeMediaSource fakeMediaSource =
- new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
+ new FakeMediaSource(adTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
// Ensure everything is preloaded.
.waitForIsLoading(true)
@@ -978,6 +1039,8 @@
.waitForIsLoading(false)
// Seek behind the midroll.
.seek(6 * C.MICROS_PER_SECOND)
+ // Wait until loading started again to assert loading events without flakiness.
+ .waitForIsLoading(true)
.play()
.waitForPlaybackState(Player.STATE_ENDED)
.build();
@@ -1007,8 +1070,8 @@
WINDOW_0 /* setPlayWhenReady=false */,
WINDOW_0 /* BUFFERING */,
contentBeforeMidroll /* READY */,
- contentAfterMidroll /* setPlayWhenReady=true */,
- midrollAd /* BUFFERING */,
+ contentAfterMidroll /* BUFFERING */,
+ midrollAd /* setPlayWhenReady=true */,
midrollAd /* READY */,
contentAfterMidroll /* ENDED */);
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
@@ -1019,7 +1082,7 @@
midrollAd /* seek adjustment */,
contentAfterMidroll /* ad transition */);
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(contentBeforeMidroll);
- assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(midrollAd);
+ assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(contentAfterMidroll);
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
.containsExactly(
contentBeforeMidroll,
@@ -1063,17 +1126,19 @@
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(contentBeforeMidroll);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
- .containsExactly(contentBeforeMidroll, midrollAd);
+ .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
- .containsExactly(contentBeforeMidroll, midrollAd);
+ .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll);
+ assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
+ .containsExactly(contentAfterMidroll);
listener.assertNoMoreEvents();
}
@Test
- public void testNotifyExternalEvents() throws Exception {
+ public void notifyExternalEvents() throws Exception {
MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE);
ActionSchedule actionSchedule =
- new ActionSchedule.Builder("AnalyticsCollectorTest")
+ new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
@@ -1146,20 +1211,19 @@
videoRendererEventListener,
audioRendererEventListener,
textRendererOutput,
- metadataRendererOutput,
- drmSessionManager) ->
+ metadataRendererOutput) ->
new Renderer[] {
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
new FakeAudioRenderer(eventHandler, audioRendererEventListener)
};
TestAnalyticsListener listener = new TestAnalyticsListener();
try {
- new ExoPlayerTestRunner.Builder()
+ new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext())
.setMediaSources(mediaSource)
.setRenderersFactory(renderersFactory)
.setAnalyticsListener(listener)
.setActionSchedule(actionSchedule)
- .build(ApplicationProvider.getApplicationContext())
+ .build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
@@ -1169,113 +1233,6 @@
return listener;
}
- private static final class FakeVideoRenderer extends FakeRenderer {
-
- private final VideoRendererEventListener.EventDispatcher eventDispatcher;
- private final DecoderCounters decoderCounters;
- private Format format;
- private boolean renderedFirstFrame;
-
- public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) {
- super(ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
- eventDispatcher = new VideoRendererEventListener.EventDispatcher(handler, eventListener);
- decoderCounters = new DecoderCounters();
- }
-
- @Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- super.onEnabled(joining);
- eventDispatcher.enabled(decoderCounters);
- renderedFirstFrame = false;
- }
-
- @Override
- protected void onStopped() throws ExoPlaybackException {
- super.onStopped();
- eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
- }
-
- @Override
- protected void onDisabled() {
- super.onDisabled();
- eventDispatcher.disabled(decoderCounters);
- }
-
- @Override
- protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
- super.onPositionReset(positionUs, joining);
- renderedFirstFrame = false;
- }
-
- @Override
- protected void onFormatChanged(Format format) {
- eventDispatcher.inputFormatChanged(format);
- eventDispatcher.decoderInitialized(
- /* decoderName= */ "fake.video.decoder",
- /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
- /* initializationDurationMs= */ 0);
- this.format = format;
- }
-
- @Override
- protected void onBufferRead() {
- if (!renderedFirstFrame) {
- eventDispatcher.videoSizeChanged(
- format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
- eventDispatcher.renderedFirstFrame(/* surface= */ null);
- renderedFirstFrame = true;
- }
- }
- }
-
- private static final class FakeAudioRenderer extends FakeRenderer {
-
- private final AudioRendererEventListener.EventDispatcher eventDispatcher;
- private final DecoderCounters decoderCounters;
- private boolean notifiedAudioSessionId;
-
- public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
- super(ExoPlayerTestRunner.Builder.AUDIO_FORMAT);
- eventDispatcher = new AudioRendererEventListener.EventDispatcher(handler, eventListener);
- decoderCounters = new DecoderCounters();
- }
-
- @Override
- protected void onEnabled(boolean joining) throws ExoPlaybackException {
- super.onEnabled(joining);
- eventDispatcher.enabled(decoderCounters);
- notifiedAudioSessionId = false;
- }
-
- @Override
- protected void onDisabled() {
- super.onDisabled();
- eventDispatcher.disabled(decoderCounters);
- }
-
- @Override
- protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
- super.onPositionReset(positionUs, joining);
- }
-
- @Override
- protected void onFormatChanged(Format format) {
- eventDispatcher.inputFormatChanged(format);
- eventDispatcher.decoderInitialized(
- /* decoderName= */ "fake.audio.decoder",
- /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
- /* initializationDurationMs= */ 0);
- }
-
- @Override
- protected void onBufferRead() {
- if (!notifiedAudioSessionId) {
- eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
- notifiedAudioSessionId = true;
- }
- }
- }
-
private static final class EventWindowAndPeriodId {
private final int windowIndex;
@@ -1373,10 +1330,10 @@
reportedEvents.add(new ReportedEvent(EVENT_SEEK_PROCESSED, eventTime));
}
+ @SuppressWarnings("deprecation")
@Override
- public void onPlaybackParametersChanged(
- EventTime eventTime, PlaybackParameters playbackParameters) {
- reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_PARAMETERS_CHANGED, eventTime));
+ public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
+ reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_SPEED_CHANGED, eventTime));
}
@Override
@@ -1390,7 +1347,7 @@
}
@Override
- public void onLoadingChanged(EventTime eventTime, boolean isLoading) {
+ public void onIsLoadingChanged(EventTime eventTime, boolean isLoading) {
reportedEvents.add(new ReportedEvent(EVENT_LOADING_CHANGED, eventTime));
}
@@ -1558,6 +1515,12 @@
reportedEvents.add(new ReportedEvent(EVENT_DRM_SESSION_RELEASED, eventTime));
}
+ @Override
+ public void onVideoFrameProcessingOffset(
+ EventTime eventTime, long totalProcessingOffsetUs, int frameCount, Format format) {
+ reportedEvents.add(new ReportedEvent(EVENT_VIDEO_FRAME_PROCESSING_OFFSET, eventTime));
+ }
+
private static final class ReportedEvent {
public final int eventType;
@@ -1568,6 +1531,16 @@
this.eventWindowAndPeriodId =
new EventWindowAndPeriodId(eventTime.windowIndex, eventTime.mediaPeriodId);
}
+
+ @Override
+ public String toString() {
+ return "ReportedEvent{"
+ + "type="
+ + eventType
+ + ", windowAndPeriodId="
+ + eventWindowAndPeriodId
+ + '}';
+ }
}
}
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java
index 00e93c0..d0a9a49 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java
@@ -609,7 +609,7 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
- new AdPlaybackState(/* adGroupTimesUs= */ C.TIME_END_OF_SOURCE)
+ new AdPlaybackState(/* adGroupTimesUs=... */ C.TIME_END_OF_SOURCE)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)));
EventTime adEventTime =
createEventTime(
@@ -820,7 +820,7 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
- new AdPlaybackState(/* adGroupTimesUs= */ 0, 5 * C.MICROS_PER_SECOND)
+ new AdPlaybackState(/* adGroupTimesUs=... */ 0, 5 * C.MICROS_PER_SECOND)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)));
EventTime adEventTime1 =
@@ -881,7 +881,7 @@
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
new AdPlaybackState(
- /* adGroupTimesUs= */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND)
+ /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)));
EventTime adEventTime1 =
@@ -932,7 +932,7 @@
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
- new AdPlaybackState(/* adGroupTimesUs= */ 0, 5 * C.MICROS_PER_SECOND)
+ new AdPlaybackState(/* adGroupTimesUs=... */ 0, 5 * C.MICROS_PER_SECOND)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)));
EventTime adEventTime1 =
@@ -1004,7 +1004,7 @@
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
new AdPlaybackState(
- /* adGroupTimesUs= */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND)
+ /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)));
EventTime adEventTime1 =
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java
new file mode 100644
index 0000000..f08d634
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.analytics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Timeline;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link PlaybackStatsListener}. */
+@RunWith(AndroidJUnit4.class)
+public final class PlaybackStatsListenerTest {
+
+ private static final AnalyticsListener.EventTime TEST_EVENT_TIME =
+ new AnalyticsListener.EventTime(
+ /* realtimeMs= */ 500,
+ Timeline.EMPTY,
+ /* windowIndex= */ 0,
+ /* mediaPeriodId= */ null,
+ /* eventPlaybackPositionMs= */ 0,
+ /* currentPlaybackPositionMs= */ 0,
+ /* totalBufferedDurationMs= */ 0);
+
+ @Test
+ public void playback_withKeepHistory_updatesStats() {
+ PlaybackStatsListener playbackStatsListener =
+ new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null);
+
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_BUFFERING);
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_READY);
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_ENDED);
+
+ @Nullable PlaybackStats playbackStats = playbackStatsListener.getPlaybackStats();
+ assertThat(playbackStats).isNotNull();
+ assertThat(playbackStats.endedCount).isEqualTo(1);
+ }
+
+ @Test
+ public void playback_withoutKeepHistory_updatesStats() {
+ PlaybackStatsListener playbackStatsListener =
+ new PlaybackStatsListener(/* keepHistory= */ false, /* callback= */ null);
+
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_BUFFERING);
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_READY);
+ playbackStatsListener.onPlaybackStateChanged(TEST_EVENT_TIME, Player.STATE_ENDED);
+
+ @Nullable PlaybackStats playbackStats = playbackStatsListener.getPlaybackStats();
+ assertThat(playbackStats).isNotNull();
+ assertThat(playbackStats.endedCount).isEqualTo(1);
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java
new file mode 100644
index 0000000..bfc657a
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DecoderAudioRendererTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.audio;
+
+import static com.google.android.exoplayer2.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
+import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_HANDLED;
+import static com.google.android.exoplayer2.RendererCapabilities.TUNNELING_NOT_SUPPORTED;
+import static com.google.android.exoplayer2.RendererCapabilities.TUNNELING_SUPPORTED;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.RendererConfiguration;
+import com.google.android.exoplayer2.decoder.DecoderException;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.SimpleDecoder;
+import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
+import com.google.android.exoplayer2.testutil.FakeSampleStream;
+import com.google.android.exoplayer2.util.MimeTypes;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link DecoderAudioRenderer}. */
+@RunWith(AndroidJUnit4.class)
+public class DecoderAudioRendererTest {
+
+ private static final Format FORMAT =
+ new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_RAW).build();
+
+ @Mock private AudioSink mockAudioSink;
+ private DecoderAudioRenderer audioRenderer;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ audioRenderer =
+ new DecoderAudioRenderer(null, null, mockAudioSink) {
+ @Override
+ public String getName() {
+ return "TestAudioRenderer";
+ }
+
+ @Override
+ @FormatSupport
+ protected int supportsFormatInternal(Format format) {
+ return FORMAT_HANDLED;
+ }
+
+ @Override
+ protected SimpleDecoder<
+ DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
+ createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
+ return new FakeDecoder();
+ }
+
+ @Override
+ protected Format getOutputFormat() {
+ return FORMAT;
+ }
+ };
+ }
+
+ @Config(sdk = 19)
+ @Test
+ public void supportsFormatAtApi19() {
+ assertThat(audioRenderer.supportsFormat(FORMAT))
+ .isEqualTo(ADAPTIVE_NOT_SEAMLESS | TUNNELING_NOT_SUPPORTED | FORMAT_HANDLED);
+ }
+
+ @Config(sdk = 21)
+ @Test
+ public void supportsFormatAtApi21() {
+ // From API 21, tunneling is supported.
+ assertThat(audioRenderer.supportsFormat(FORMAT))
+ .isEqualTo(ADAPTIVE_NOT_SEAMLESS | TUNNELING_SUPPORTED | FORMAT_HANDLED);
+ }
+
+ @Test
+ public void immediatelyReadEndOfStreamPlaysAudioSinkToEndOfStream() throws Exception {
+ audioRenderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {FORMAT},
+ new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false),
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ true,
+ /* offsetUs= */ 0);
+ audioRenderer.setCurrentStreamFinal();
+ when(mockAudioSink.isEnded()).thenReturn(true);
+ while (!audioRenderer.isEnded()) {
+ audioRenderer.render(0, 0);
+ }
+ verify(mockAudioSink, times(1)).playToEndOfStream();
+ audioRenderer.disable();
+ audioRenderer.reset();
+ verify(mockAudioSink, times(1)).reset();
+ }
+
+ private static final class FakeDecoder
+ extends SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, DecoderException> {
+
+ public FakeDecoder() {
+ super(new DecoderInputBuffer[1], new SimpleOutputBuffer[1]);
+ }
+
+ @Override
+ public String getName() {
+ return "FakeDecoder";
+ }
+
+ @Override
+ protected DecoderInputBuffer createInputBuffer() {
+ return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
+ }
+
+ @Override
+ protected SimpleOutputBuffer createOutputBuffer() {
+ return new SimpleOutputBuffer(this::releaseOutputBuffer);
+ }
+
+ @Override
+ protected DecoderException createUnexpectedDecodeException(Throwable error) {
+ return new DecoderException("Unexpected decode error", error);
+ }
+
+ @Override
+ protected DecoderException decode(
+ DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) {
+ if (inputBuffer.isEndOfStream()) {
+ outputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
+ }
+ return null;
+ }
+
+ }
+
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
index 7982163..9689a32 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
@@ -21,7 +21,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
@@ -38,7 +37,7 @@
* data (i.e., the {@link android.media.AudioTrack#write} methods just return 0), so these tests are
* currently limited to verifying behavior that doesn't rely on consuming data, and the position
* will stay at its initial value. For example, we can't verify {@link
- * AudioSink#handleBuffer(ByteBuffer, long)} handling a complete buffer, or queueing audio then
+ * AudioSink#handleBuffer(ByteBuffer, long, int)} handling a complete buffer, or queueing audio then
* draining to the end of the stream. This could be worked around by having a test-only mode where
* {@link DefaultAudioSink} automatically treats audio as consumed.
*/
@@ -77,51 +76,57 @@
@Test
public void handlesBufferAfterReset() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After reset and re-configure we can successfully queue more input.
defaultAudioSink.reset();
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
}
@Test
public void handlesBufferAfterReset_withPlaybackParameters() throws Exception {
- PlaybackParameters playbackParameters = new PlaybackParameters(1.5f);
- defaultAudioSink.setPlaybackParameters(playbackParameters);
+ defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After reset and re-configure we can successfully queue more input.
defaultAudioSink.reset();
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
- assertThat(defaultAudioSink.getPlaybackParameters()).isEqualTo(playbackParameters);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
+ assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
}
@Test
public void handlesBufferAfterReset_withFormatChange() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After reset and re-configure we can successfully queue more input.
defaultAudioSink.reset();
configureDefaultAudioSink(CHANNEL_COUNT_MONO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
}
@Test
public void handlesBufferAfterReset_withFormatChangeAndPlaybackParameters() throws Exception {
- PlaybackParameters playbackParameters = new PlaybackParameters(1.5f);
- defaultAudioSink.setPlaybackParameters(playbackParameters);
+ defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After reset and re-configure we can successfully queue more input.
defaultAudioSink.reset();
configureDefaultAudioSink(CHANNEL_COUNT_MONO);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
- assertThat(defaultAudioSink.getPlaybackParameters()).isEqualTo(playbackParameters);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
+ assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
}
@Test
@@ -130,7 +135,8 @@
CHANNEL_COUNT_STEREO,
/* trimStartFrames= */ TRIM_100_MS_FRAME_COUNT,
/* trimEndFrames= */ 0);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
assertThat(arrayAudioBufferSink.output)
.hasLength(
@@ -145,7 +151,8 @@
CHANNEL_COUNT_STEREO,
/* trimStartFrames= */ 0,
/* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
assertThat(arrayAudioBufferSink.output)
.hasLength(
@@ -160,7 +167,8 @@
CHANNEL_COUNT_STEREO,
/* trimStartFrames= */ TRIM_100_MS_FRAME_COUNT,
/* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT);
- defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0);
+ defaultAudioSink.handleBuffer(
+ createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
assertThat(arrayAudioBufferSink.output)
.hasLength(
@@ -173,14 +181,18 @@
public void getCurrentPosition_returnsPositionFromFirstBuffer() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
- createDefaultSilenceBuffer(), /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND);
+ createDefaultSilenceBuffer(),
+ /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
+ /* encodedAccessUnitCount= */ 1);
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
.isEqualTo(5 * C.MICROS_PER_SECOND);
defaultAudioSink.reset();
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
- createDefaultSilenceBuffer(), /* presentationTimeUs= */ 8 * C.MICROS_PER_SECOND);
+ createDefaultSilenceBuffer(),
+ /* presentationTimeUs= */ 8 * C.MICROS_PER_SECOND,
+ /* encodedAccessUnitCount= */ 1);
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
.isEqualTo(8 * C.MICROS_PER_SECOND);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java
index 6783c96..fac1c4e 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java
@@ -49,7 +49,7 @@
}
@Test
- public void testEnabledProcessor_isActive() throws Exception {
+ public void enabledProcessor_isActive() throws Exception {
// Given an enabled processor.
silenceSkippingAudioProcessor.setEnabled(true);
@@ -61,7 +61,7 @@
}
@Test
- public void testDisabledProcessor_isNotActive() throws Exception {
+ public void disabledProcessor_isNotActive() throws Exception {
// Given a disabled processor.
silenceSkippingAudioProcessor.setEnabled(false);
@@ -73,7 +73,7 @@
}
@Test
- public void testDefaultProcessor_isNotEnabled() throws Exception {
+ public void defaultProcessor_isNotEnabled() throws Exception {
// Given a processor in its default state.
// When reconfigured.
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
@@ -83,7 +83,7 @@
}
@Test
- public void testSkipInSilentSignal_skipsEverything() throws Exception {
+ public void skipInSilentSignal_skipsEverything() throws Exception {
// Given a signal with only noise.
InputBufferProvider inputBufferProvider =
getInputBufferProviderForAlternatingSilenceAndNoise(
@@ -105,7 +105,7 @@
}
@Test
- public void testSkipInNoisySignal_skipsNothing() throws Exception {
+ public void skipInNoisySignal_skipsNothing() throws Exception {
// Given a signal with only silence.
InputBufferProvider inputBufferProvider =
getInputBufferProviderForAlternatingSilenceAndNoise(
@@ -129,8 +129,7 @@
}
@Test
- public void testSkipInAlternatingTestSignal_hasCorrectOutputAndSkippedFrameCounts()
- throws Exception {
+ public void skipInAlternatingTestSignal_hasCorrectOutputAndSkippedFrameCounts() throws Exception {
// Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider =
getInputBufferProviderForAlternatingSilenceAndNoise(
@@ -154,7 +153,7 @@
}
@Test
- public void testSkipWithSmallerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts()
+ public void skipWithSmallerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts()
throws Exception {
// Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider =
@@ -179,7 +178,7 @@
}
@Test
- public void testSkipWithLargerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts()
+ public void skipWithLargerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts()
throws Exception {
// Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider =
@@ -204,7 +203,7 @@
}
@Test
- public void testSkipThenFlush_resetsSkippedFrameCount() throws Exception {
+ public void skipThenFlush_resetsSkippedFrameCount() throws Exception {
// Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider =
getInputBufferProviderForAlternatingSilenceAndNoise(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java
deleted file mode 100644
index 0d8d2da..0000000
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.audio;
-
-import static com.google.android.exoplayer2.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
-import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_HANDLED;
-import static com.google.android.exoplayer2.RendererCapabilities.TUNNELING_NOT_SUPPORTED;
-import static com.google.android.exoplayer2.RendererCapabilities.TUNNELING_SUPPORTED;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.RendererConfiguration;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
-import com.google.android.exoplayer2.testutil.FakeSampleStream;
-import com.google.android.exoplayer2.util.MimeTypes;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-/** Unit test for {@link SimpleDecoderAudioRenderer}. */
-@RunWith(AndroidJUnit4.class)
-public class SimpleDecoderAudioRendererTest {
-
- private static final Format FORMAT = Format.createSampleFormat(null, MimeTypes.AUDIO_RAW, 0);
-
- @Mock private AudioSink mockAudioSink;
- private SimpleDecoderAudioRenderer audioRenderer;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- audioRenderer =
- new SimpleDecoderAudioRenderer(null, null, null, false, mockAudioSink) {
- @Override
- @FormatSupport
- protected int supportsFormatInternal(
- @Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) {
- return FORMAT_HANDLED;
- }
-
- @Override
- protected SimpleDecoder<
- DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException>
- createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
- return new FakeDecoder();
- }
-
- @Override
- protected Format getOutputFormat() {
- return FORMAT;
- }
- };
- }
-
- @Config(sdk = 19)
- @Test
- public void testSupportsFormatAtApi19() {
- assertThat(audioRenderer.supportsFormat(FORMAT))
- .isEqualTo(ADAPTIVE_NOT_SEAMLESS | TUNNELING_NOT_SUPPORTED | FORMAT_HANDLED);
- }
-
- @Config(sdk = 21)
- @Test
- public void testSupportsFormatAtApi21() {
- // From API 21, tunneling is supported.
- assertThat(audioRenderer.supportsFormat(FORMAT))
- .isEqualTo(ADAPTIVE_NOT_SEAMLESS | TUNNELING_SUPPORTED | FORMAT_HANDLED);
- }
-
- @Test
- public void testImmediatelyReadEndOfStreamPlaysAudioSinkToEndOfStream() throws Exception {
- audioRenderer.enable(
- RendererConfiguration.DEFAULT,
- new Format[] {FORMAT},
- new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false),
- 0,
- false,
- 0);
- audioRenderer.setCurrentStreamFinal();
- when(mockAudioSink.isEnded()).thenReturn(true);
- while (!audioRenderer.isEnded()) {
- audioRenderer.render(0, 0);
- }
- verify(mockAudioSink, times(1)).playToEndOfStream();
- audioRenderer.disable();
- audioRenderer.reset();
- verify(mockAudioSink, times(1)).reset();
- }
-
- private static final class FakeDecoder
- extends SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, AudioDecoderException> {
-
- public FakeDecoder() {
- super(new DecoderInputBuffer[1], new SimpleOutputBuffer[1]);
- }
-
- @Override
- public String getName() {
- return "FakeDecoder";
- }
-
- @Override
- protected DecoderInputBuffer createInputBuffer() {
- return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
- }
-
- @Override
- protected SimpleOutputBuffer createOutputBuffer() {
- return new SimpleOutputBuffer(this);
- }
-
- @Override
- protected AudioDecoderException createUnexpectedDecodeException(Throwable error) {
- return new AudioDecoderException("Unexpected decode error", error);
- }
-
- @Override
- protected AudioDecoderException decode(DecoderInputBuffer inputBuffer,
- SimpleOutputBuffer outputBuffer, boolean reset) {
- if (inputBuffer.isEndOfStream()) {
- outputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
- }
- return null;
- }
-
- }
-
-}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java
index e6b4487..46e1794 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java
@@ -48,7 +48,7 @@
}
@Test
- public void testReconfigureWithSameSampleRate() throws Exception {
+ public void reconfigureWithSameSampleRate() throws Exception {
// When configured for resampling from 44.1 kHz to 48 kHz, the output sample rate is correct.
sonicAudioProcessor.setOutputSampleRateHz(48000);
AudioFormat outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
@@ -65,7 +65,7 @@
}
@Test
- public void testNoSampleRateChange() throws Exception {
+ public void noSampleRateChange() throws Exception {
// Configure for resampling 44.1 kHz to 48 kHz.
sonicAudioProcessor.setOutputSampleRateHz(48000);
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
@@ -78,7 +78,7 @@
}
@Test
- public void testIsActiveWithSpeedChange() throws Exception {
+ public void isActiveWithSpeedChange() throws Exception {
sonicAudioProcessor.setSpeed(1.5f);
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
sonicAudioProcessor.flush();
@@ -86,21 +86,13 @@
}
@Test
- public void testIsActiveWithPitchChange() throws Exception {
- sonicAudioProcessor.setPitch(1.5f);
- sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
- sonicAudioProcessor.flush();
- assertThat(sonicAudioProcessor.isActive()).isTrue();
- }
-
- @Test
- public void testIsNotActiveWithNoChange() throws Exception {
+ public void isNotActiveWithNoChange() throws Exception {
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
assertThat(sonicAudioProcessor.isActive()).isFalse();
}
@Test
- public void testDoesNotSupportNon16BitInput() throws Exception {
+ public void doesNotSupportNon16BitInput() throws Exception {
try {
sonicAudioProcessor.configure(
new AudioFormat(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java
index d12319a..57fab8b 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java
@@ -69,7 +69,7 @@
@Config(sdk = 26)
@Test
- public void testAdjustSingleKeyResponseDataV26() {
+ public void adjustSingleKeyResponseDataV26() {
// Everything but the keys should be removed. Within each key only the k, kid and kty parameters
// should remain. Any "-" and "_" characters in the k and kid values should be replaced with "+"
// and "/".
@@ -87,7 +87,7 @@
@Config(sdk = 26)
@Test
- public void testAdjustMultiKeyResponseDataV26() {
+ public void adjustMultiKeyResponseDataV26() {
// Everything but the keys should be removed. Within each key only the k, kid and kty parameters
// should remain. Any "-" and "_" characters in the k and kid values should be replaced with "+"
// and "/".
@@ -107,14 +107,14 @@
@Config(sdk = 27)
@Test
- public void testAdjustResponseDataV27() {
+ public void adjustResponseDataV27() {
// Response should be unchanged.
assertThat(ClearKeyUtil.adjustResponseData(SINGLE_KEY_RESPONSE)).isEqualTo(SINGLE_KEY_RESPONSE);
}
@Config(sdk = 26)
@Test
- public void testAdjustRequestDataV26() {
+ public void adjustRequestDataV26() {
// We expect "+" and "/" to be replaced with "-" and "_" respectively, for "kids".
byte[] expected =
Util.getUtf8Bytes(
@@ -130,7 +130,7 @@
@Config(sdk = 27)
@Test
- public void testAdjustRequestDataV27() {
+ public void adjustRequestDataV27() {
// Request should be unchanged.
assertThat(ClearKeyUtil.adjustRequestData(KEY_REQUEST)).isEqualTo(KEY_REQUEST);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java
index e3dd679..c36c6cf 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java
@@ -25,6 +25,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import java.util.HashMap;
import org.junit.After;
import org.junit.Before;
@@ -39,9 +40,9 @@
@LooperMode(LooperMode.Mode.PAUSED)
public class OfflineLicenseHelperTest {
- private OfflineLicenseHelper<?> offlineLicenseHelper;
+ private OfflineLicenseHelper offlineLicenseHelper;
@Mock private MediaDrmCallback mediaDrmCallback;
- @Mock private ExoMediaDrm<ExoMediaCrypto> mediaDrm;
+ @Mock private ExoMediaDrm mediaDrm;
@Before
public void setUp() throws Exception {
@@ -51,11 +52,12 @@
.thenReturn(
new ExoMediaDrm.KeyRequest(/* data= */ new byte[0], /* licenseServerUrl= */ ""));
offlineLicenseHelper =
- new OfflineLicenseHelper<>(
+ new OfflineLicenseHelper(
C.WIDEVINE_UUID,
- new ExoMediaDrm.AppManagedProvider<>(mediaDrm),
+ new ExoMediaDrm.AppManagedProvider(mediaDrm),
mediaDrmCallback,
- null);
+ /* optionalKeyRequestParameters= */ null,
+ new MediaSourceEventDispatcher());
}
@After
@@ -65,7 +67,7 @@
}
@Test
- public void testDownloadRenewReleaseKey() throws Exception {
+ public void downloadRenewReleaseKey() throws Exception {
setStubLicenseAndPlaybackDurationValues(1000, 200);
byte[] keySetId = {2, 5, 8};
@@ -86,7 +88,7 @@
}
@Test
- public void testDownloadLicenseFailsIfNullInitData() throws Exception {
+ public void downloadLicenseFailsIfNullInitData() throws Exception {
try {
offlineLicenseHelper.downloadLicense(null);
fail();
@@ -96,7 +98,7 @@
}
@Test
- public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception {
+ public void downloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception {
setStubLicenseAndPlaybackDurationValues(1000, 200);
try {
@@ -108,7 +110,7 @@
}
@Test
- public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception {
+ public void downloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception {
setDefaultStubKeySetId();
byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData());
@@ -117,7 +119,7 @@
}
@Test
- public void testGetLicenseDurationRemainingSec() throws Exception {
+ public void getLicenseDurationRemainingSec() throws Exception {
long licenseDuration = 1000;
int playbackDuration = 200;
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
@@ -133,7 +135,7 @@
}
@Test
- public void testGetLicenseDurationRemainingSecExpiredLicense() throws Exception {
+ public void getLicenseDurationRemainingSecExpiredLicense() throws Exception {
long licenseDuration = 0;
int playbackDuration = 0;
setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
index 11f967d..f816d1d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
@@ -19,6 +19,7 @@
import static com.google.android.exoplayer2.testutil.TestUtil.assertBufferInfosEqual;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.media.MediaCodec;
import android.media.MediaFormat;
@@ -33,8 +34,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
+import org.robolectric.annotation.LooperMode;
/** Unit tests for {@link AsynchronousMediaCodecAdapter}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public class AsynchronousMediaCodecAdapterTest {
private AsynchronousMediaCodecAdapter adapter;
@@ -45,7 +48,7 @@
@Before
public void setUp() throws IOException {
- handlerThread = new HandlerThread("TestHandlerThread");
+ handlerThread = new HandlerThread("TestHandler");
handlerThread.start();
looper = handlerThread.getLooper();
codec = MediaCodec.createByCodecName("h264");
@@ -96,7 +99,7 @@
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1));
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(1);
}
@@ -113,7 +116,7 @@
adapter.flush();
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThrows(
IllegalStateException.class,
() -> {
@@ -165,7 +168,7 @@
() -> adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, info1));
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(1);
assertBufferInfosEqual(info1, bufferInfo);
}
@@ -183,7 +186,7 @@
adapter.flush();
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
}
@@ -220,7 +223,7 @@
adapter.flush();
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThat(adapter.getOutputFormat()).isEqualTo(format);
}
@@ -233,7 +236,7 @@
adapter.shutdown();
// Wait until all tasks have been handled.
- Shadows.shadowOf(handlerThread.getLooper()).idle();
+ Shadows.shadowOf(looper).idle();
assertThat(onCodecStartCalled.get()).isEqualTo(1);
}
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
new file mode 100644
index 0000000..c7020b4
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.mediacodec;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.doAnswer;
+
+import android.media.MediaCodec;
+import android.os.HandlerThread;
+import android.os.Looper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.decoder.CryptoInfo;
+import com.google.android.exoplayer2.util.ConditionVariable;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowLooper;
+
+/** Unit tests for {@link AsynchronousMediaCodecBufferEnqueuer}. */
+@RunWith(AndroidJUnit4.class)
+public class AsynchronousMediaCodecBufferEnqueuerTest {
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private AsynchronousMediaCodecBufferEnqueuer enqueuer;
+ private TestHandlerThread handlerThread;
+ @Mock private ConditionVariable mockConditionVariable;
+
+ @Before
+ public void setUp() throws IOException {
+ MediaCodec codec = MediaCodec.createByCodecName("h264");
+ handlerThread = new TestHandlerThread("TestHandlerThread");
+ enqueuer =
+ new AsynchronousMediaCodecBufferEnqueuer(codec, handlerThread, mockConditionVariable);
+ }
+
+ @After
+ public void tearDown() {
+ enqueuer.shutdown();
+
+ assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0);
+ }
+
+ @Test
+ public void queueInputBuffer_withPendingCryptoExceptionSet_throwsCryptoException() {
+ enqueuer.setPendingRuntimeException(
+ new MediaCodec.CryptoException(/* errorCode= */ 0, /* detailMessage= */ null));
+ enqueuer.start();
+
+ assertThrows(
+ MediaCodec.CryptoException.class,
+ () ->
+ enqueuer.queueInputBuffer(
+ /* index= */ 0,
+ /* offset= */ 0,
+ /* size= */ 0,
+ /* presentationTimeUs= */ 0,
+ /* flags= */ 0));
+ }
+
+ @Test
+ public void queueInputBuffer_withPendingIllegalStateExceptionSet_throwsIllegalStateException() {
+ enqueuer.start();
+ enqueuer.setPendingRuntimeException(new IllegalStateException());
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ enqueuer.queueInputBuffer(
+ /* index= */ 0,
+ /* offset= */ 0,
+ /* size= */ 0,
+ /* presentationTimeUs= */ 0,
+ /* flags= */ 0));
+ }
+
+ @Test
+ public void queueInputBuffer_multipleTimes_limitsObjectsAllocation() {
+ enqueuer.start();
+ Looper looper = handlerThread.getLooper();
+ ShadowLooper shadowLooper = Shadows.shadowOf(looper);
+
+ for (int cycle = 0; cycle < 100; cycle++) {
+ // Enqueue 10 messages to looper.
+ for (int i = 0; i < 10; i++) {
+ enqueuer.queueInputBuffer(
+ /* index= */ i,
+ /* offset= */ 0,
+ /* size= */ 0,
+ /* presentationTimeUs= */ i,
+ /* flags= */ 0);
+ }
+ // Execute all messages.
+ shadowLooper.idle();
+ }
+
+ assertThat(AsynchronousMediaCodecBufferEnqueuer.getInstancePoolSize()).isEqualTo(10);
+ }
+
+ @Test
+ public void queueSecureInputBuffer_withPendingCryptoException_throwsCryptoException() {
+ enqueuer.setPendingRuntimeException(
+ new MediaCodec.CryptoException(/* errorCode= */ 0, /* detailMessage= */ null));
+ enqueuer.start();
+ CryptoInfo info = createCryptoInfo();
+
+ assertThrows(
+ MediaCodec.CryptoException.class,
+ () ->
+ enqueuer.queueSecureInputBuffer(
+ /* index= */ 0,
+ /* offset= */ 0,
+ /* info= */ info,
+ /* presentationTimeUs= */ 0,
+ /* flags= */ 0));
+ }
+
+ @Test
+ public void queueSecureInputBuffer_codecThrewIllegalStateException_throwsIllegalStateException() {
+ enqueuer.setPendingRuntimeException(new IllegalStateException());
+ enqueuer.start();
+ CryptoInfo info = createCryptoInfo();
+
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ enqueuer.queueSecureInputBuffer(
+ /* index= */ 0,
+ /* offset= */ 0,
+ /* info= */ info,
+ /* presentationTimeUs= */ 0,
+ /* flags= */ 0));
+ }
+
+ @Test
+ public void queueSecureInputBuffer_multipleTimes_limitsObjectsAllocation() {
+ enqueuer.start();
+ Looper looper = handlerThread.getLooper();
+ CryptoInfo info = createCryptoInfo();
+ ShadowLooper shadowLooper = Shadows.shadowOf(looper);
+
+ for (int cycle = 0; cycle < 100; cycle++) {
+ // Enqueue 10 messages to looper.
+ for (int i = 0; i < 10; i++) {
+ enqueuer.queueSecureInputBuffer(
+ /* index= */ i,
+ /* offset= */ 0,
+ /* info= */ info,
+ /* presentationTimeUs= */ i,
+ /* flags= */ 0);
+ }
+ // Execute all messages.
+ shadowLooper.idle();
+ }
+
+ assertThat(AsynchronousMediaCodecBufferEnqueuer.getInstancePoolSize()).isEqualTo(10);
+ }
+
+ @Test
+ public void flush_withoutStart_works() {
+ enqueuer.flush();
+ }
+
+ @Test
+ public void flush_onInterruptedException_throwsIllegalStateException()
+ throws InterruptedException {
+ doAnswer(
+ invocation -> {
+ throw new InterruptedException();
+ })
+ .doNothing()
+ .when(mockConditionVariable)
+ .block();
+
+ enqueuer.start();
+
+ assertThrows(IllegalStateException.class, () -> enqueuer.flush());
+ }
+
+ @Test
+ public void flush_multipleTimes_works() {
+ enqueuer.start();
+
+ enqueuer.flush();
+ enqueuer.flush();
+ }
+
+ @Test
+ public void shutdown_withoutStart_works() {
+ enqueuer.shutdown();
+ }
+
+ @Test
+ public void shutdown_multipleTimes_works() {
+ enqueuer.start();
+
+ enqueuer.shutdown();
+ enqueuer.shutdown();
+ }
+
+ @Test
+ public void shutdown_onInterruptedException_throwsIllegalStateException()
+ throws InterruptedException {
+ doAnswer(
+ invocation -> {
+ throw new InterruptedException();
+ })
+ .doNothing()
+ .when(mockConditionVariable)
+ .block();
+
+ enqueuer.start();
+
+ assertThrows(IllegalStateException.class, () -> enqueuer.shutdown());
+ }
+
+ private static class TestHandlerThread extends HandlerThread {
+ private static final AtomicLong INSTANCES_STARTED = new AtomicLong(0);
+
+ TestHandlerThread(String name) {
+ super(name);
+ }
+
+ @Override
+ public synchronized void start() {
+ super.start();
+ INSTANCES_STARTED.incrementAndGet();
+ }
+
+ @Override
+ public boolean quit() {
+ boolean quit = super.quit();
+ if (quit) {
+ INSTANCES_STARTED.decrementAndGet();
+ }
+ return quit;
+ }
+ }
+
+ private static CryptoInfo createCryptoInfo() {
+ CryptoInfo info = new CryptoInfo();
+ int numSubSamples = 5;
+ int[] numBytesOfClearData = new int[] {0, 1, 2, 3};
+ int[] numBytesOfEncryptedData = new int[] {4, 5, 6, 7};
+ byte[] key = new byte[] {0, 1, 2, 3};
+ byte[] iv = new byte[] {4, 5, 6, 7};
+ @C.CryptoMode int mode = C.CRYPTO_MODE_AES_CBC;
+ int encryptedBlocks = 16;
+ int clearBlocks = 8;
+ info.set(
+ numSubSamples,
+ numBytesOfClearData,
+ numBytesOfEncryptedData,
+ key,
+ iv,
+ mode,
+ encryptedBlocks,
+ clearBlocks);
+ return info;
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java
index 2ce2e50..7ea55b1 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.media.MediaCodec;
import android.media.MediaFormat;
@@ -27,6 +28,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -35,9 +37,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
/** Unit tests for {@link DedicatedThreadAsyncMediaCodecAdapter}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public class DedicatedThreadAsyncMediaCodecAdapterTest {
private DedicatedThreadAsyncMediaCodecAdapter adapter;
@@ -49,7 +53,12 @@
public void setUp() throws IOException {
codec = MediaCodec.createByCodecName("h264");
handlerThread = new TestHandlerThread("TestHandlerThread");
- adapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, handlerThread);
+ adapter =
+ new DedicatedThreadAsyncMediaCodecAdapter(
+ codec,
+ /* enableAsynchronousQueueing= */ false,
+ /* trackType= */ C.TRACK_TYPE_VIDEO,
+ handlerThread);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo();
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java
index 3693e49..587e2f2 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java
@@ -96,22 +96,11 @@
/* colorTransfer= */ C.COLOR_TRANSFER_SDR,
/* hdrStaticInfo= */ new byte[] {1, 2, 3, 4, 5, 6, 7});
Format format =
- Format.createVideoSampleFormat(
- /* id= */ null,
- MimeTypes.VIDEO_AV1,
- /* codecs= */ "av01.0.21M.10",
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* rotationDegrees= */ Format.NO_VALUE,
- /* pixelWidthHeightRatio= */ 0,
- /* projectionData= */ null,
- /* stereoMode= */ Format.NO_VALUE,
- /* colorInfo= */ colorInfo,
- /* drmInitData */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_AV1)
+ .setCodecs("av01.0.21M.10")
+ .setColorInfo(colorInfo)
+ .build();
assertCodecProfileAndLevelForFormat(
format,
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10,
@@ -127,22 +116,11 @@
/* colorTransfer= */ C.COLOR_TRANSFER_HLG,
/* hdrStaticInfo= */ null);
Format format =
- Format.createVideoSampleFormat(
- /* id= */ null,
- MimeTypes.VIDEO_AV1,
- /* codecs= */ "av01.0.21M.10",
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* rotationDegrees= */ Format.NO_VALUE,
- /* pixelWidthHeightRatio= */ 0,
- /* projectionData= */ null,
- /* stereoMode= */ Format.NO_VALUE,
- /* colorInfo= */ colorInfo,
- /* drmInitData */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_AV1)
+ .setCodecs("av01.0.21M.10")
+ .setColorInfo(colorInfo)
+ .build();
assertCodecProfileAndLevelForFormat(
format,
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10,
@@ -161,52 +139,20 @@
@Test
public void getCodecProfileAndLevel_rejectsNullCodecString() {
- Format format =
- Format.createVideoSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.VIDEO_UNKNOWN,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
+ Format format = new Format.Builder().setCodecs(null).build();
assertThat(MediaCodecUtil.getCodecProfileAndLevel(format)).isNull();
}
@Test
public void getCodecProfileAndLevel_rejectsEmptyCodecString() {
- Format format =
- Format.createVideoSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.VIDEO_UNKNOWN,
- /* codecs= */ "",
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
+ Format format = new Format.Builder().setCodecs("").build();
assertThat(MediaCodecUtil.getCodecProfileAndLevel(format)).isNull();
}
private static void assertCodecProfileAndLevelForCodecsString(
- String mimeType, String codecs, int profile, int level) {
+ String sampleMimeType, String codecs, int profile, int level) {
Format format =
- Format.createVideoSampleFormat(
- /* id= */ null,
- mimeType,
- /* codecs= */ codecs,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
+ new Format.Builder().setSampleMimeType(sampleMimeType).setCodecs(codecs).build();
assertCodecProfileAndLevelForFormat(format, profile, level);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java
index 1232d8b..cfe9cf2 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.media.MediaCodec;
import android.media.MediaFormat;
@@ -27,6 +28,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -35,9 +37,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
/** Unit tests for {@link MultiLockAsyncMediaCodecAdapter}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public class MultiLockAsyncMediaCodecAdapterTest {
private MultiLockAsyncMediaCodecAdapter adapter;
@@ -49,7 +53,9 @@
public void setUp() throws IOException {
codec = MediaCodec.createByCodecName("h264");
handlerThread = new TestHandlerThread("TestHandlerThread");
- adapter = new MultiLockAsyncMediaCodecAdapter(codec, handlerThread);
+ adapter =
+ new MultiLockAsyncMediaCodecAdapter(
+ codec, /* enableAsynchronousQueueing= */ false, C.TRACK_TYPE_VIDEO, handlerThread);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo();
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java
index 482a686..4d1b4f6 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/MetadataRendererTest.java
@@ -32,7 +32,6 @@
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -65,7 +64,7 @@
0x00, 0x00, 0x00, 0x00)); // CRC_32 (ignored, check happens at extraction).
private static final Format EMSG_FORMAT =
- Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_EMSG).build();
private final EventMessageEncoder eventMessageEncoder = new EventMessageEncoder();
@@ -146,8 +145,10 @@
new FakeSampleStream(
EMSG_FORMAT,
/* eventDispatcher= */ null,
- Arrays.asList(new FakeSampleStreamItem(input), FakeSampleStreamItem.END_OF_STREAM_ITEM),
- 0),
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 0,
+ new FakeSampleStreamItem(input),
+ FakeSampleStreamItem.END_OF_STREAM_ITEM),
/* offsetUs= */ 0L);
renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the format
renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the data
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoderTest.java
new file mode 100644
index 0000000..39de14b
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AppInfoTableDecoderTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.metadata.dvbsi;
+
+import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
+import com.google.android.exoplayer2.testutil.TestUtil;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link AppInfoTableDecoder}. */
+@RunWith(AndroidJUnit4.class)
+public final class AppInfoTableDecoderTest {
+
+ private static final String TYPICAL_FILE = "dvbsi/ait_typical.bin";
+ private static final String NO_URL_BASE_FILE = "dvbsi/ait_no_url_base.bin";
+ private static final String NO_URL_PATH_FILE = "dvbsi/ait_no_url_path.bin";
+
+ @Test
+ public void decode_typical() throws Exception {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(TYPICAL_FILE)));
+
+ assertThat(metadata.length()).isEqualTo(2);
+ Metadata.Entry firstEntry = metadata.get(0);
+ assertThat(firstEntry).isInstanceOf(AppInfoTable.class);
+ assertThat(((AppInfoTable) firstEntry).controlCode)
+ .isEqualTo(AppInfoTable.CONTROL_CODE_AUTOSTART);
+ assertThat(((AppInfoTable) firstEntry).url).isEqualTo("http://example.com/path/foo");
+ Metadata.Entry secondEntry = metadata.get(1);
+ assertThat(secondEntry).isInstanceOf(AppInfoTable.class);
+ assertThat(((AppInfoTable) secondEntry).controlCode)
+ .isEqualTo(AppInfoTable.CONTROL_CODE_PRESENT);
+ assertThat(((AppInfoTable) secondEntry).url).isEqualTo("http://google.com/path/bar");
+ }
+
+ @Test
+ public void decode_noUrlBase() throws Exception {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(NO_URL_BASE_FILE)));
+
+ assertThat(metadata).isNull();
+ }
+
+ @Test
+ public void decode_noUrlPath() throws Exception {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(NO_URL_PATH_FILE)));
+
+ assertThat(metadata).isNull();
+ }
+
+ @Test
+ public void decode_failsIfPositionNonZero() {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decode_failsIfBufferHasNoArray() {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data = buffer.data.asReadOnlyBuffer();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decode_failsIfArrayOffsetNonZero() {
+ AppInfoTableDecoder decoder = new AppInfoTableDecoder();
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+ buffer.data = buffer.data.slice();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ private static byte[] readTestFile(String name) throws IOException {
+ return TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), name);
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/icy/IcyDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/icy/IcyDecoderTest.java
index fcb958e..49cca03 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/icy/IcyDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/icy/IcyDecoderTest.java
@@ -15,16 +15,18 @@
*/
package com.google.android.exoplayer2.metadata.icy;
+import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_16;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.testutil.TestUtil;
-import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -186,10 +188,28 @@
assertThat(streamInfo.url).isNull();
}
- private static MetadataInputBuffer createMetadataInputBuffer(byte[] data) {
- MetadataInputBuffer metadataInputBuffer = new MetadataInputBuffer();
- metadataInputBuffer.data = ByteBuffer.allocate(data.length).put(data);
- metadataInputBuffer.data.flip();
- return metadataInputBuffer;
+ @Test
+ public void decode_failsIfPositionNonZero() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decode_failsIfBufferHasNoArray() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data = buffer.data.asReadOnlyBuffer();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decode_failsIfArrayOffsetNonZero() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+ buffer.data = buffer.data.slice();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
}
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java
index 92fa147..90c2e7d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java
@@ -16,7 +16,10 @@
package com.google.android.exoplayer2.metadata.scte35;
import static com.google.android.exoplayer2.C.TIME_UNSET;
+import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
+import static com.google.android.exoplayer2.testutil.TestUtil.createMetadataInputBuffer;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.metadata.Metadata;
@@ -42,7 +45,7 @@
}
@Test
- public void testWrappedAroundTimeSignalCommand() {
+ public void wrappedAroundTimeSignalCommand() {
byte[] rawTimeSignalSection = new byte[] {
0, // table_id.
(byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
@@ -162,9 +165,35 @@
assertThat(command.availsExpected).isEqualTo(2);
}
+ @Test
+ public void decodeFailsIfPositionNonZero() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeFailsIfBufferHasNoArray() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data = buffer.data.asReadOnlyBuffer();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
+ @Test
+ public void decodeFailsIfArrayOffsetNonZero() {
+ MetadataInputBuffer buffer = createMetadataInputBuffer(createByteArray(1, 2, 3));
+ buffer.data.position(1);
+ buffer.data = buffer.data.slice();
+
+ assertThrows(IllegalArgumentException.class, () -> decoder.decode(buffer));
+ }
+
private Metadata feedInputBuffer(byte[] data, long timeUs, long subsampleOffset) {
inputBuffer.clear();
inputBuffer.data = ByteBuffer.allocate(data.length).put(data);
+ inputBuffer.data.flip();
inputBuffer.timeUs = timeUs;
inputBuffer.subsampleOffsetUs = subsampleOffset;
return decoder.decode(inputBuffer);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java
index 7abfa44..cec0d07 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java
@@ -56,7 +56,7 @@
}
@Test
- public void testLoadNoDataThrowsIOException() throws Exception {
+ public void loadNoDataThrowsIOException() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_no_data.exi");
try {
actionFile.load();
@@ -67,7 +67,7 @@
}
@Test
- public void testLoadIncompleteHeaderThrowsIOException() throws Exception {
+ public void loadIncompleteHeaderThrowsIOException() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_incomplete_header.exi");
try {
actionFile.load();
@@ -78,7 +78,7 @@
}
@Test
- public void testLoadZeroActions() throws Exception {
+ public void loadZeroActions() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_zero_actions.exi");
DownloadRequest[] actions = actionFile.load();
assertThat(actions).isNotNull();
@@ -86,7 +86,7 @@
}
@Test
- public void testLoadOneAction() throws Exception {
+ public void loadOneAction() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_one_action.exi");
DownloadRequest[] actions = actionFile.load();
assertThat(actions).hasLength(1);
@@ -94,7 +94,7 @@
}
@Test
- public void testLoadTwoActions() throws Exception {
+ public void loadTwoActions() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_two_actions.exi");
DownloadRequest[] actions = actionFile.load();
assertThat(actions).hasLength(2);
@@ -103,7 +103,7 @@
}
@Test
- public void testLoadUnsupportedVersion() throws Exception {
+ public void loadUnsupportedVersion() throws Exception {
ActionFile actionFile = getActionFile("offline/action_file_unsupported_version.exi");
try {
actionFile.load();
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
index d18bc86..5fa9ae0 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
@@ -119,11 +119,11 @@
@Before
public void setUp() {
- FakeRenderer videoRenderer = new FakeRenderer(VIDEO_FORMAT_LOW, VIDEO_FORMAT_HIGH);
- FakeRenderer audioRenderer = new FakeRenderer(audioFormatUs, audioFormatZh);
- FakeRenderer textRenderer = new FakeRenderer(textFormatUs, textFormatZh);
+ FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
+ FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
+ FakeRenderer textRenderer = new FakeRenderer(C.TRACK_TYPE_TEXT);
RenderersFactory renderersFactory =
- (handler, videoListener, audioListener, metadata, text, drm) ->
+ (handler, videoListener, audioListener, metadata, text) ->
new Renderer[] {textRenderer, audioRenderer, videoRenderer};
downloadHelper =
@@ -454,40 +454,25 @@
}
private static Format createVideoFormat(int bitrate) {
- return Format.createVideoSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- /* bitrate= */ bitrate,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 480,
- /* height= */ 360,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(bitrate)
+ .build();
}
private static Format createAudioFormat(String language) {
- return Format.createAudioSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 48000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ C.SELECTION_FLAG_DEFAULT,
- /* language= */ language);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setLanguage(language)
+ .build();
}
private static Format createTextFormat(String language) {
- return Format.createTextSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.TEXT_VTT,
- /* selectionFlags= */ C.SELECTION_FLAG_DEFAULT,
- /* language= */ language);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.TEXT_VTT)
+ .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
+ .setLanguage(language)
+ .build();
}
private static void assertSingleTrackSelectionEquals(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java
index a55b1e1..c5b00b0 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java
@@ -45,7 +45,7 @@
}
@Test
- public void testMergeRequests_withDifferentIds_fails() {
+ public void mergeRequests_withDifferentIds_fails() {
DownloadRequest request1 =
new DownloadRequest(
"id1",
@@ -71,7 +71,7 @@
}
@Test
- public void testMergeRequests_withDifferentTypes_fails() {
+ public void mergeRequests_withDifferentTypes_fails() {
DownloadRequest request1 =
new DownloadRequest(
"id1",
@@ -97,7 +97,7 @@
}
@Test
- public void testMergeRequest_withSameRequest() {
+ public void mergeRequest_withSameRequest() {
DownloadRequest request1 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest mergedRequest = request1.copyWithMergedRequest(request1);
@@ -105,7 +105,7 @@
}
@Test
- public void testMergeRequests_withEmptyStreamKeys() {
+ public void mergeRequests_withEmptyStreamKeys() {
DownloadRequest request1 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request2 = createRequest(uri1);
@@ -118,7 +118,7 @@
}
@Test
- public void testMergeRequests_withOverlappingStreamKeys() {
+ public void mergeRequests_withOverlappingStreamKeys() {
StreamKey streamKey1 = new StreamKey(0, 1, 2);
StreamKey streamKey2 = new StreamKey(3, 4, 5);
StreamKey streamKey3 = new StreamKey(6, 7, 8);
@@ -134,7 +134,7 @@
}
@Test
- public void testMergeRequests_withDifferentFields() {
+ public void mergeRequests_withDifferentFields() {
byte[] data1 = new byte[] {0, 1, 2};
byte[] data2 = new byte[] {3, 4, 5};
DownloadRequest request1 =
@@ -167,7 +167,7 @@
}
@Test
- public void testParcelable() {
+ public void parcelable() {
ArrayList<StreamKey> streamKeys = new ArrayList<>();
streamKeys.add(new StreamKey(1, 2, 3));
streamKeys.add(new StreamKey(4, 5, 6));
@@ -191,7 +191,7 @@
@SuppressWarnings("EqualsWithItself")
@Test
- public void testEquals() {
+ public void equals() {
DownloadRequest request1 = createRequest(uri1);
assertThat(request1.equals(request1)).isTrue();
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java
deleted file mode 100644
index a18c278..0000000
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/offline/StreamKeyTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.offline;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Parcel;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link StreamKey}. */
-@RunWith(AndroidJUnit4.class)
-public class StreamKeyTest {
-
- @Test
- public void testParcelable() {
- StreamKey streamKeyToParcel = new StreamKey(1, 2, 3);
- Parcel parcel = Parcel.obtain();
- streamKeyToParcel.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- StreamKey streamKeyFromParcel = StreamKey.CREATOR.createFromParcel(parcel);
- assertThat(streamKeyFromParcel).isEqualTo(streamKeyToParcel);
-
- parcel.recycle();
- }
-}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java
index 57ed933..ae0c431 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java
@@ -26,6 +26,7 @@
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.ClippingMediaSource.IllegalClippingException;
+import com.google.android.exoplayer2.source.MaskingMediaSource.DummyTimeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
@@ -62,7 +63,7 @@
}
@Test
- public void testNoClipping() throws IOException {
+ public void noClipping() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
@@ -81,7 +82,7 @@
}
@Test
- public void testClippingUnseekableWindowThrows() throws IOException {
+ public void clippingUnseekableWindowThrows() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
@@ -101,7 +102,27 @@
}
@Test
- public void testClippingStart() throws IOException {
+ public void clippingUnseekableWindowWithUnknownDurationThrows() throws IOException {
+ Timeline timeline =
+ new SinglePeriodTimeline(
+ /* durationUs= */ C.TIME_UNSET,
+ /* isSeekable= */ false,
+ /* isDynamic= */ false,
+ /* isLive= */ false);
+
+ // If the unseekable window isn't clipped, clipping succeeds.
+ getClippedTimeline(timeline, /* startUs= */ 0, TEST_PERIOD_DURATION_US);
+ try {
+ // If the unseekable window is clipped, clipping fails.
+ getClippedTimeline(timeline, /* startUs= */ 1, TEST_PERIOD_DURATION_US);
+ fail("Expected clipping to fail.");
+ } catch (IllegalClippingException e) {
+ assertThat(e.reason).isEqualTo(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
+ }
+ }
+
+ @Test
+ public void clippingStart() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
@@ -118,7 +139,7 @@
}
@Test
- public void testClippingEnd() throws IOException {
+ public void clippingEnd() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
@@ -135,13 +156,11 @@
}
@Test
- public void testClippingStartAndEndInitial() throws IOException {
+ public void clippingStartAndEndInitial() throws IOException {
// Timeline that's dynamic and not seekable. A child source might report such a timeline prior
// to it having loaded sufficient data to establish its duration and seekability. Such timelines
// should not result in clipping failure.
- Timeline timeline =
- new SinglePeriodTimeline(
- C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
+ Timeline timeline = new DummyTimeline(/* tag= */ null);
Timeline clippedTimeline =
getClippedTimeline(
@@ -153,7 +172,7 @@
}
@Test
- public void testClippingToEndOfSourceWithDurationSetsDuration() throws IOException {
+ public void clippingToEndOfSourceWithDurationSetsDuration() throws IOException {
// Create a child timeline that has a known duration.
Timeline timeline =
new SinglePeriodTimeline(
@@ -170,7 +189,7 @@
}
@Test
- public void testClippingToEndOfSourceWithUnsetDurationDoesNotSetDuration() throws IOException {
+ public void clippingToEndOfSourceWithUnsetDurationDoesNotSetDuration() throws IOException {
// Create a child timeline that has an unknown duration.
Timeline timeline =
new SinglePeriodTimeline(
@@ -187,7 +206,7 @@
}
@Test
- public void testClippingStartAndEnd() throws IOException {
+ public void clippingStartAndEnd() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
@@ -205,7 +224,7 @@
}
@Test
- public void testClippingFromDefaultPosition() throws IOException {
+ public void clippingFromDefaultPosition() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
@@ -228,7 +247,7 @@
}
@Test
- public void testAllowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
+ public void allowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
@@ -279,7 +298,7 @@
}
@Test
- public void testAllowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
+ public void allowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
@@ -330,7 +349,7 @@
}
@Test
- public void testDisallowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
+ public void disallowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
@@ -382,7 +401,7 @@
}
@Test
- public void testDisallowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
+ public void disallowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
@@ -432,7 +451,7 @@
}
@Test
- public void testWindowAndPeriodIndices() throws IOException {
+ public void windowAndPeriodIndices() throws IOException {
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(1, 111, true, false, TEST_PERIOD_DURATION_US));
@@ -452,7 +471,7 @@
}
@Test
- public void testEventTimeWithinClippedRange() throws IOException {
+ public void eventTimeWithinClippedRange() throws IOException {
MediaLoadData mediaLoadData =
getClippingMediaSourceMediaLoadData(
/* clippingStartUs= */ TEST_CLIP_AMOUNT_US,
@@ -465,7 +484,7 @@
}
@Test
- public void testEventTimeOutsideClippedRange() throws IOException {
+ public void eventTimeOutsideClippedRange() throws IOException {
MediaLoadData mediaLoadData =
getClippingMediaSourceMediaLoadData(
/* clippingStartUs= */ TEST_CLIP_AMOUNT_US,
@@ -478,7 +497,7 @@
}
@Test
- public void testUnsetEventTime() throws IOException {
+ public void unsetEventTime() throws IOException {
MediaLoadData mediaLoadData =
getClippingMediaSourceMediaLoadData(
/* clippingStartUs= */ TEST_CLIP_AMOUNT_US,
@@ -490,7 +509,7 @@
}
@Test
- public void testEventTimeWithUnsetDuration() throws IOException {
+ public void eventTimeWithUnsetDuration() throws IOException {
MediaLoadData mediaLoadData =
getClippingMediaSourceMediaLoadData(
/* clippingStartUs= */ TEST_CLIP_AMOUNT_US,
@@ -635,7 +654,7 @@
clippedTimelines[0].getUidOfPeriod(/* periodIndex= */ 0),
/* windowSequenceNumber= */ 0));
for (int i = 0; i < additionalTimelines.length; i++) {
- fakeMediaSource.setNewSourceInfo(additionalTimelines[i], /* newManifest= */ null);
+ fakeMediaSource.setNewSourceInfo(additionalTimelines[i]);
clippedTimelines[i + 1] = testRunner.assertTimelineChangeBlocking();
}
testRunner.releasePeriod(mediaPeriod);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java
index c996aad..c2f9ad0 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java
@@ -31,7 +31,7 @@
* position among all sub-loaders.
*/
@Test
- public void testGetBufferedPositionUsReturnsMinimumLoaderBufferedPosition() {
+ public void getBufferedPositionUsReturnsMinimumLoaderBufferedPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -46,7 +46,7 @@
* position that is not {@link C#TIME_END_OF_SOURCE} among all sub-loaders.
*/
@Test
- public void testGetBufferedPositionUsReturnsMinimumNonEndOfSourceLoaderBufferedPosition() {
+ public void getBufferedPositionUsReturnsMinimumNonEndOfSourceLoaderBufferedPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -61,11 +61,11 @@
}
/**
- * Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns
- * {@link C#TIME_END_OF_SOURCE} when all sub-loaders have buffered till end-of-source.
+ * Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns {@link
+ * C#TIME_END_OF_SOURCE} when all sub-loaders have buffered till end-of-source.
*/
@Test
- public void testGetBufferedPositionUsReturnsEndOfSourceWhenAllLoaderBufferedTillEndOfSource() {
+ public void getBufferedPositionUsReturnsEndOfSourceWhenAllLoaderBufferedTillEndOfSource() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ C.TIME_END_OF_SOURCE,
@@ -84,7 +84,7 @@
* load position among all sub-loaders.
*/
@Test
- public void testGetNextLoadPositionUsReturnMinimumLoaderNextLoadPositionUs() {
+ public void getNextLoadPositionUsReturnMinimumLoaderNextLoadPositionUs() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2001);
FakeSequenceableLoader loader2 =
@@ -99,7 +99,7 @@
* load position that is not {@link C#TIME_END_OF_SOURCE} among all sub-loaders.
*/
@Test
- public void testGetNextLoadPositionUsReturnMinimumNonEndOfSourceLoaderNextLoadPositionUs() {
+ public void getNextLoadPositionUsReturnMinimumNonEndOfSourceLoaderNextLoadPositionUs() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -113,11 +113,11 @@
}
/**
- * Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns
- * {@link C#TIME_END_OF_SOURCE} when all sub-loaders have next load position at end-of-source.
+ * Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns {@link
+ * C#TIME_END_OF_SOURCE} when all sub-loaders have next load position at end-of-source.
*/
@Test
- public void testGetNextLoadPositionUsReturnsEndOfSourceWhenAllLoaderLoadingLastChunk() {
+ public void getNextLoadPositionUsReturnsEndOfSourceWhenAllLoaderLoadingLastChunk() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
@@ -135,7 +135,7 @@
* current playback position.
*/
@Test
- public void testContinueLoadingOnlyAllowFurthestBehindLoaderToLoadIfNotBehindPlaybackPosition() {
+ public void continueLoadingOnlyAllowFurthestBehindLoaderToLoadIfNotBehindPlaybackPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -149,11 +149,11 @@
}
/**
- * Tests that {@link CompositeSequenceableLoader#continueLoading(long)} allows all loaders
- * with next load position behind current playback position to continue loading.
+ * Tests that {@link CompositeSequenceableLoader#continueLoading(long)} allows all loaders with
+ * next load position behind current playback position to continue loading.
*/
@Test
- public void testContinueLoadingReturnAllowAllLoadersBehindPlaybackPositionToLoad() {
+ public void continueLoadingReturnAllowAllLoadersBehindPlaybackPositionToLoad() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -170,11 +170,11 @@
}
/**
- * Tests that {@link CompositeSequenceableLoader#continueLoading(long)} does not allow loader
- * with next load position at end-of-source to continue loading.
+ * Tests that {@link CompositeSequenceableLoader#continueLoading(long)} does not allow loader with
+ * next load position at end-of-source to continue loading.
*/
@Test
- public void testContinueLoadingOnlyNotAllowEndOfSourceLoaderToLoad() {
+ public void continueLoadingOnlyNotAllowEndOfSourceLoaderToLoad() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
@@ -191,11 +191,11 @@
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} returns true if the loader
- * with minimum next load position can make progress if next load positions are not behind
- * current playback position.
+ * with minimum next load position can make progress if next load positions are not behind current
+ * playback position.
*/
@Test
- public void testContinueLoadingReturnTrueIfFurthestBehindLoaderCanMakeProgress() {
+ public void continueLoadingReturnTrueIfFurthestBehindLoaderCanMakeProgress() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
@@ -214,7 +214,7 @@
* minimum next load position.
*/
@Test
- public void testContinueLoadingReturnTrueIfLoaderBehindPlaybackPositionCanMakeProgress() {
+ public void continueLoadingReturnTrueIfLoaderBehindPlaybackPositionCanMakeProgress() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java
index d2330f2..cf2e3e8 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java
@@ -65,7 +65,7 @@
}
@Test
- public void testPlaylistChangesAfterPreparation() throws IOException, InterruptedException {
+ public void playlistChangesAfterPreparation() throws IOException, InterruptedException {
Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertEmpty(timeline);
@@ -187,7 +187,7 @@
}
@Test
- public void testPlaylistChangesBeforePreparation() throws IOException, InterruptedException {
+ public void playlistChangesBeforePreparation() throws IOException, InterruptedException {
FakeMediaSource[] childSources = createMediaSources(4);
mediaSource.addMediaSource(childSources[0]);
mediaSource.addMediaSource(childSources[1]);
@@ -220,7 +220,7 @@
}
@Test
- public void testPlaylistWithLazyMediaSource() throws IOException, InterruptedException {
+ public void playlistWithLazyMediaSource() throws IOException, InterruptedException {
// Create some normal (immediately preparing) sources and some lazy sources whose timeline
// updates need to be triggered.
FakeMediaSource[] fastSources = createMediaSources(2);
@@ -246,8 +246,7 @@
// Trigger source info refresh for lazy source and check that the timeline now contains all
// information for all windows.
- testRunner.runOnPlaybackThread(
- () -> lazySources[1].setNewSourceInfo(createFakeTimeline(8), null));
+ testRunner.runOnPlaybackThread(() -> lazySources[1].setNewSourceInfo(createFakeTimeline(8)));
timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
TimelineAsserts.assertWindowTags(timeline, 111, 999);
@@ -281,8 +280,7 @@
// Trigger source info refresh for lazy media source. Assert that now all information is
// available again and the previously created period now also finished preparing.
- testRunner.runOnPlaybackThread(
- () -> lazySources[3].setNewSourceInfo(createFakeTimeline(7), null));
+ testRunner.runOnPlaybackThread(() -> lazySources[3].setNewSourceInfo(createFakeTimeline(7)));
timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9);
TimelineAsserts.assertWindowTags(timeline, 888, 111, 222, 999);
@@ -303,7 +301,7 @@
}
@Test
- public void testEmptyTimelineMediaSource() throws IOException, InterruptedException {
+ public void emptyTimelineMediaSource() throws IOException, InterruptedException {
Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertEmpty(timeline);
@@ -359,7 +357,7 @@
}
@Test
- public void testDynamicChangeOfEmptyTimelines() throws IOException {
+ public void dynamicChangeOfEmptyTimelines() throws IOException {
FakeMediaSource[] childSources =
new FakeMediaSource[] {
new FakeMediaSource(Timeline.EMPTY),
@@ -372,21 +370,21 @@
Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertEmpty(timeline);
- childSources[0].setNewSourceInfo(nonEmptyTimeline, /* newManifest== */ null);
+ childSources[0].setNewSourceInfo(nonEmptyTimeline);
timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1);
- childSources[2].setNewSourceInfo(nonEmptyTimeline, /* newManifest== */ null);
+ childSources[2].setNewSourceInfo(nonEmptyTimeline);
timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 1);
- childSources[1].setNewSourceInfo(nonEmptyTimeline, /* newManifest== */ null);
+ childSources[1].setNewSourceInfo(nonEmptyTimeline);
timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
}
@Test
- public void testIllegalArguments() {
+ public void illegalArguments() {
MediaSource validSource = new FakeMediaSource(createFakeTimeline(1));
// Null sources.
@@ -407,7 +405,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationAddSingle() throws Exception {
+ public void customCallbackBeforePreparationAddSingle() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -422,7 +420,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationAddMultiple() throws Exception {
+ public void customCallbackBeforePreparationAddMultiple() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -439,7 +437,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationAddSingleWithIndex() throws Exception {
+ public void customCallbackBeforePreparationAddSingleWithIndex() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -457,7 +455,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationAddMultipleWithIndex() throws Exception {
+ public void customCallbackBeforePreparationAddMultipleWithIndex() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -475,7 +473,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationRemove() throws Exception {
+ public void customCallbackBeforePreparationRemove() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -492,7 +490,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationMove() throws Exception {
+ public void customCallbackBeforePreparationMove() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -510,7 +508,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationAddSingle() throws Exception {
+ public void customCallbackAfterPreparationAddSingle() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -527,7 +525,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationAddMultiple() throws Exception {
+ public void customCallbackAfterPreparationAddMultiple() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -547,7 +545,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationAddSingleWithIndex() throws Exception {
+ public void customCallbackAfterPreparationAddSingleWithIndex() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -564,7 +562,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationAddMultipleWithIndex() throws Exception {
+ public void customCallbackAfterPreparationAddMultipleWithIndex() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -585,7 +583,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationRemove() throws Exception {
+ public void customCallbackAfterPreparationRemove() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -604,7 +602,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationMove() throws Exception {
+ public void customCallbackAfterPreparationMove() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
testRunner.prepareSource();
@@ -628,7 +626,7 @@
}
@Test
- public void testCustomCallbackIsCalledAfterRelease() throws Exception {
+ public void customCallbackIsCalledAfterRelease() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
CountDownLatch callbackCalledCondition = new CountDownLatch(1);
try {
@@ -654,7 +652,7 @@
}
@Test
- public void testPeriodCreationWithAds() throws IOException, InterruptedException {
+ public void periodCreationWithAds() throws IOException, InterruptedException {
// Create concatenated media source with ad child source.
Timeline timelineContentOnly =
new FakeTimeline(
@@ -668,7 +666,7 @@
false,
10 * C.MICROS_PER_SECOND,
FakeTimeline.createAdPlaybackState(
- /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 0)));
+ /* adsPerAdGroup= */ 1, /* adGroupTimesUs=... */ 0)));
FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly);
FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds);
mediaSource.addMediaSource(mediaSourceContentOnly);
@@ -710,7 +708,7 @@
}
@Test
- public void testAtomicTimelineWindowOrder() throws IOException {
+ public void atomicTimelineWindowOrder() throws IOException {
// Release default test runner with non-atomic media source and replace with new test runner.
testRunner.release();
ConcatenatingMediaSource mediaSource =
@@ -751,7 +749,7 @@
}
@Test
- public void testNestedTimeline() throws IOException {
+ public void nestedTimeline() throws IOException {
ConcatenatingMediaSource nestedSource1 =
new ConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0));
ConcatenatingMediaSource nestedSource2 =
@@ -798,7 +796,7 @@
}
@Test
- public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException {
+ public void removeChildSourceWithActiveMediaPeriod() throws IOException {
FakeMediaSource childSource = createFakeMediaSource();
mediaSource.addMediaSource(childSource);
Timeline timeline = testRunner.prepareSource();
@@ -814,7 +812,7 @@
}
@Test
- public void testDuplicateMediaSources() throws IOException, InterruptedException {
+ public void duplicateMediaSources() throws IOException, InterruptedException {
Timeline childTimeline = new FakeTimeline(/* windowCount= */ 2);
FakeMediaSource childSource = new FakeMediaSource(childTimeline);
@@ -839,7 +837,7 @@
new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 5),
new MediaPeriodId(childPeriodUid1, /* windowSequenceNumber= */ 7));
// Assert that only one manifest load is reported because the source is reused.
- testRunner.assertCompletedManifestLoads(/* windowIndices= */ 0);
+ testRunner.assertCompletedManifestLoads(/* windowIndices=... */ 0);
assertCompletedAllMediaPeriodLoads(timeline);
testRunner.releaseSource();
@@ -847,7 +845,7 @@
}
@Test
- public void testDuplicateNestedMediaSources() throws IOException, InterruptedException {
+ public void duplicateNestedMediaSources() throws IOException, InterruptedException {
Timeline childTimeline = new FakeTimeline(/* windowCount= */ 1);
FakeMediaSource childSource = new FakeMediaSource(childTimeline);
ConcatenatingMediaSource nestedConcatenation = new ConcatenatingMediaSource();
@@ -872,7 +870,7 @@
new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 3),
new MediaPeriodId(childPeriodUid, /* windowSequenceNumber= */ 4));
// Assert that only one manifest load is needed because the source is reused.
- testRunner.assertCompletedManifestLoads(/* windowIndices= */ 0);
+ testRunner.assertCompletedManifestLoads(/* windowIndices=... */ 0);
assertCompletedAllMediaPeriodLoads(timeline);
testRunner.releaseSource();
@@ -880,7 +878,7 @@
}
@Test
- public void testClear() throws Exception {
+ public void clear() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
final FakeMediaSource preparedChildSource = createFakeMediaSource();
final FakeMediaSource unpreparedChildSource = new FakeMediaSource(/* timeline= */ null);
@@ -901,7 +899,7 @@
}
@Test
- public void testReleaseAndReprepareSource() throws IOException {
+ public void releaseAndReprepareSource() throws IOException {
FakeMediaSource[] fakeMediaSources = createMediaSources(/* count= */ 2);
mediaSource.addMediaSource(fakeMediaSources[0]); // Child source with 1 period.
mediaSource.addMediaSource(fakeMediaSources[1]); // Child source with 2 periods.
@@ -922,7 +920,7 @@
}
@Test
- public void testChildTimelineChangeWithActiveMediaPeriod() throws IOException {
+ public void childTimelineChangeWithActiveMediaPeriod() throws IOException {
FakeMediaSource[] nestedChildSources = createMediaSources(/* count= */ 2);
ConcatenatingMediaSource childSource = new ConcatenatingMediaSource(nestedChildSources);
mediaSource.addMediaSource(childSource);
@@ -942,7 +940,7 @@
}
@Test
- public void testChildSourceIsNotPreparedWithLazyPreparation() throws IOException {
+ public void childSourceIsNotPreparedWithLazyPreparation() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
@@ -958,7 +956,7 @@
}
@Test
- public void testChildSourceIsPreparedWithLazyPreparationAfterPeriodCreation() throws IOException {
+ public void childSourceIsPreparedWithLazyPreparationAfterPeriodCreation() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
@@ -977,7 +975,7 @@
}
@Test
- public void testChildSourceWithLazyPreparationOnlyPreparesSourceOnce() throws IOException {
+ public void childSourceWithLazyPreparationOnlyPreparesSourceOnce() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
@@ -999,7 +997,7 @@
}
@Test
- public void testRemoveUnpreparedChildSourceWithLazyPreparation() throws IOException {
+ public void removeUnpreparedChildSourceWithLazyPreparation() throws IOException {
FakeMediaSource[] childSources = createMediaSources(/* count= */ 2);
mediaSource =
new ConcatenatingMediaSource(
@@ -1015,7 +1013,7 @@
}
@Test
- public void testSetShuffleOrderBeforePreparation() throws Exception {
+ public void setShuffleOrderBeforePreparation() throws Exception {
mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0));
mediaSource.addMediaSources(
Arrays.asList(createFakeMediaSource(), createFakeMediaSource(), createFakeMediaSource()));
@@ -1025,7 +1023,7 @@
}
@Test
- public void testSetShuffleOrderAfterPreparation() throws Exception {
+ public void setShuffleOrderAfterPreparation() throws Exception {
mediaSource.addMediaSources(
Arrays.asList(createFakeMediaSource(), createFakeMediaSource(), createFakeMediaSource()));
testRunner.prepareSource();
@@ -1036,7 +1034,7 @@
}
@Test
- public void testCustomCallbackBeforePreparationSetShuffleOrder() throws Exception {
+ public void customCallbackBeforePreparationSetShuffleOrder() throws Exception {
CountDownLatch runnableInvoked = new CountDownLatch(1);
DummyMainThread dummyMainThread = new DummyMainThread();
@@ -1053,7 +1051,7 @@
}
@Test
- public void testCustomCallbackAfterPreparationSetShuffleOrder() throws Exception {
+ public void customCallbackAfterPreparationSetShuffleOrder() throws Exception {
DummyMainThread dummyMainThread = new DummyMainThread();
try {
mediaSource.addMediaSources(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java
new file mode 100644
index 0000000..3c9d518
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactoryTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.util.MimeTypes;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link DefaultMediaSourceFactory}. */
+@RunWith(AndroidJUnit4.class)
+public final class DefaultMediaSourceFactoryTest {
+
+ private static final String URI_MEDIA = "http://exoplayer.dev/video";
+ private static final String URI_TEXT = "http://exoplayer.dev/text";
+
+ @Test
+ public void createMediaSource_withoutMimeType_progressiveSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA).build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ProgressiveMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withTag_tagInSource() {
+ Object tag = new Object();
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA).setTag(tag).build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource.getTag()).isEqualTo(tag);
+ }
+
+ @Test
+ public void createMediaSource_withPath_progressiveSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.mp3").build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ProgressiveMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withNull_usesNonNullDefaults() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA).build();
+
+ MediaSource mediaSource =
+ defaultMediaSourceFactory
+ .setDrmSessionManager(null)
+ .setDrmHttpDataSourceFactory(null)
+ .setLoadErrorHandlingPolicy(null)
+ .createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isNotNull();
+ }
+
+ @Test
+ public void createMediaSource_withSubtitle_isMergingMediaSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ List<MediaItem.Subtitle> subtitles =
+ Arrays.asList(
+ new MediaItem.Subtitle(Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "en"),
+ new MediaItem.Subtitle(
+ Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "de", C.SELECTION_FLAG_DEFAULT));
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_MEDIA).setSubtitles(subtitles).build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(MergingMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withSubtitle_hasTag() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ Object tag = new Object();
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setTag(tag)
+ .setSourceUri(URI_MEDIA)
+ .setSubtitles(
+ Collections.singletonList(
+ new MediaItem.Subtitle(Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "en")))
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource.getTag()).isEqualTo(tag);
+ }
+
+ @Test
+ public void createMediaSource_withStartPosition_isClippingMediaSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_MEDIA).setClipStartPositionMs(1000L).build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withEndPosition_isClippingMediaSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_MEDIA).setClipEndPositionMs(1000L).build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_relativeToDefaultPosition_isClippingMediaSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setClipRelativeToDefaultPosition(true)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_defaultToEnd_isNotClippingMediaSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setClipEndPositionMs(C.TIME_END_OF_SOURCE)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(ProgressiveMediaSource.class);
+ }
+
+ @Test
+ public void getSupportedTypes_coreModule_onlyOther() {
+ int[] supportedTypes =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext())
+ .getSupportedTypes();
+
+ assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER);
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java
index fa7c2f0..f938ffe 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java
@@ -47,7 +47,7 @@
}
@Test
- public void testSingleLoopTimeline() throws IOException {
+ public void singleLoopTimeline() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1);
TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
@@ -66,7 +66,7 @@
}
@Test
- public void testMultiLoopTimeline() throws IOException {
+ public void multiLoopTimeline() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3);
TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1);
@@ -87,7 +87,7 @@
}
@Test
- public void testInfiniteLoopTimeline() throws IOException {
+ public void infiniteLoopTimeline() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE);
TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
@@ -105,7 +105,7 @@
}
@Test
- public void testEmptyTimelineLoop() throws IOException {
+ public void emptyTimelineLoop() throws IOException {
Timeline timeline = getLoopingTimeline(Timeline.EMPTY, 1);
TimelineAsserts.assertEmpty(timeline);
@@ -117,17 +117,17 @@
}
@Test
- public void testSingleLoopPeriodCreation() throws Exception {
+ public void singleLoopPeriodCreation() throws Exception {
testMediaPeriodCreation(multiWindowTimeline, /* loopCount= */ 1);
}
@Test
- public void testMultiLoopPeriodCreation() throws Exception {
+ public void multiLoopPeriodCreation() throws Exception {
testMediaPeriodCreation(multiWindowTimeline, /* loopCount= */ 3);
}
@Test
- public void testInfiniteLoopPeriodCreation() throws Exception {
+ public void infiniteLoopPeriodCreation() throws Exception {
testMediaPeriodCreation(multiWindowTimeline, /* loopCount= */ Integer.MAX_VALUE);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java
new file mode 100644
index 0000000..d201782
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaPeriodTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
+import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
+import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
+import com.google.android.exoplayer2.trackselection.TrackSelection;
+import java.util.concurrent.CountDownLatch;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.LooperMode;
+
+/** Unit test for {@link MergingMediaPeriod}. */
+@RunWith(AndroidJUnit4.class)
+@LooperMode(LooperMode.Mode.PAUSED)
+public final class MergingMediaPeriodTest {
+
+ private static final Format childFormat11 = new Format.Builder().setId("1_1").build();
+ private static final Format childFormat12 = new Format.Builder().setId("1_2").build();
+ private static final Format childFormat21 = new Format.Builder().setId("2_1").build();
+ private static final Format childFormat22 = new Format.Builder().setId("2_2").build();
+
+ @Test
+ public void getTrackGroups_returnsAllChildTrackGroups() throws Exception {
+ MergingMediaPeriod mergingMediaPeriod =
+ prepareMergingPeriod(
+ new MergingPeriodDefinition(/* timeOffsetUs= */ 0, childFormat11, childFormat12),
+ new MergingPeriodDefinition(/* timeOffsetUs= */ 0, childFormat21, childFormat22));
+
+ assertThat(mergingMediaPeriod.getTrackGroups().length).isEqualTo(4);
+ assertThat(mergingMediaPeriod.getTrackGroups().get(0).getFormat(0)).isEqualTo(childFormat11);
+ assertThat(mergingMediaPeriod.getTrackGroups().get(1).getFormat(0)).isEqualTo(childFormat12);
+ assertThat(mergingMediaPeriod.getTrackGroups().get(2).getFormat(0)).isEqualTo(childFormat21);
+ assertThat(mergingMediaPeriod.getTrackGroups().get(3).getFormat(0)).isEqualTo(childFormat22);
+ }
+
+ @Test
+ public void selectTracks_createsSampleStreamsFromChildPeriods() throws Exception {
+ MergingMediaPeriod mergingMediaPeriod =
+ prepareMergingPeriod(
+ new MergingPeriodDefinition(/* timeOffsetUs= */ 0, childFormat11, childFormat12),
+ new MergingPeriodDefinition(/* timeOffsetUs= */ 0, childFormat21, childFormat22));
+
+ TrackSelection selectionForChild1 =
+ new FixedTrackSelection(mergingMediaPeriod.getTrackGroups().get(1), /* track= */ 0);
+ TrackSelection selectionForChild2 =
+ new FixedTrackSelection(mergingMediaPeriod.getTrackGroups().get(2), /* track= */ 0);
+ SampleStream[] streams = new SampleStream[4];
+ mergingMediaPeriod.selectTracks(
+ /* selections= */ new TrackSelection[] {null, selectionForChild1, selectionForChild2, null},
+ /* mayRetainStreamFlags= */ new boolean[] {false, false, false, false},
+ streams,
+ /* streamResetFlags= */ new boolean[] {false, false, false, false},
+ /* positionUs= */ 0);
+
+ assertThat(streams[0]).isNull();
+ assertThat(streams[3]).isNull();
+
+ FormatHolder formatHolder = new FormatHolder();
+ DecoderInputBuffer inputBuffer =
+ new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
+ assertThat(streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ true))
+ .isEqualTo(C.RESULT_FORMAT_READ);
+ assertThat(formatHolder.format).isEqualTo(childFormat12);
+
+ assertThat(streams[2].readData(formatHolder, inputBuffer, /* formatRequired= */ true))
+ .isEqualTo(C.RESULT_FORMAT_READ);
+ assertThat(formatHolder.format).isEqualTo(childFormat21);
+ }
+
+ @Test
+ public void
+ selectTracks_withPeriodOffsets_selectTracksWithOffset_andCreatesSampleStreamsCorrectingOffset()
+ throws Exception {
+ MergingMediaPeriod mergingMediaPeriod =
+ prepareMergingPeriod(
+ new MergingPeriodDefinition(/* timeOffsetUs= */ 0, childFormat11, childFormat12),
+ new MergingPeriodDefinition(/* timeOffsetUs= */ -3000, childFormat21, childFormat22));
+
+ TrackSelection selectionForChild1 =
+ new FixedTrackSelection(mergingMediaPeriod.getTrackGroups().get(0), /* track= */ 0);
+ TrackSelection selectionForChild2 =
+ new FixedTrackSelection(mergingMediaPeriod.getTrackGroups().get(2), /* track= */ 0);
+ SampleStream[] streams = new SampleStream[2];
+ mergingMediaPeriod.selectTracks(
+ /* selections= */ new TrackSelection[] {selectionForChild1, selectionForChild2},
+ /* mayRetainStreamFlags= */ new boolean[] {false, false},
+ streams,
+ /* streamResetFlags= */ new boolean[] {false, false},
+ /* positionUs= */ 0);
+ FormatHolder formatHolder = new FormatHolder();
+ DecoderInputBuffer inputBuffer =
+ new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
+ streams[0].readData(formatHolder, inputBuffer, /* formatRequired= */ true);
+ streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ true);
+
+ FakeMediaPeriodWithSelectTracksPosition childMediaPeriod1 =
+ (FakeMediaPeriodWithSelectTracksPosition) mergingMediaPeriod.getChildPeriod(0);
+ assertThat(childMediaPeriod1.selectTracksPositionUs).isEqualTo(0);
+ assertThat(streams[0].readData(formatHolder, inputBuffer, /* formatRequired= */ false))
+ .isEqualTo(C.RESULT_BUFFER_READ);
+ assertThat(inputBuffer.timeUs).isEqualTo(0L);
+
+ FakeMediaPeriodWithSelectTracksPosition childMediaPeriod2 =
+ (FakeMediaPeriodWithSelectTracksPosition) mergingMediaPeriod.getChildPeriod(1);
+ assertThat(childMediaPeriod2.selectTracksPositionUs).isEqualTo(3000L);
+ assertThat(streams[1].readData(formatHolder, inputBuffer, /* formatRequired= */ false))
+ .isEqualTo(C.RESULT_BUFFER_READ);
+ assertThat(inputBuffer.timeUs).isEqualTo(0L);
+ }
+
+ private MergingMediaPeriod prepareMergingPeriod(MergingPeriodDefinition... definitions)
+ throws Exception {
+ MediaPeriod[] mediaPeriods = new MediaPeriod[definitions.length];
+ long[] timeOffsetsUs = new long[definitions.length];
+ for (int i = 0; i < definitions.length; i++) {
+ timeOffsetsUs[i] = definitions[i].timeOffsetUs;
+ TrackGroup[] trackGroups = new TrackGroup[definitions[i].formats.length];
+ for (int j = 0; j < definitions[i].formats.length; j++) {
+ trackGroups[j] = new TrackGroup(definitions[i].formats[j]);
+ }
+ mediaPeriods[i] =
+ new FakeMediaPeriodWithSelectTracksPosition(
+ new TrackGroupArray(trackGroups), new EventDispatcher());
+ }
+ MergingMediaPeriod mergingMediaPeriod =
+ new MergingMediaPeriod(
+ new DefaultCompositeSequenceableLoaderFactory(), timeOffsetsUs, mediaPeriods);
+
+ CountDownLatch prepareCountDown = new CountDownLatch(1);
+ mergingMediaPeriod.prepare(
+ new MediaPeriod.Callback() {
+ @Override
+ public void onPrepared(MediaPeriod mediaPeriod) {
+ prepareCountDown.countDown();
+ }
+
+ @Override
+ public void onContinueLoadingRequested(MediaPeriod source) {
+ mergingMediaPeriod.continueLoading(/* positionUs= */ 0);
+ }
+ },
+ /* positionUs= */ 0);
+ prepareCountDown.await();
+
+ return mergingMediaPeriod;
+ }
+
+ private static final class FakeMediaPeriodWithSelectTracksPosition extends FakeMediaPeriod {
+
+ public long selectTracksPositionUs;
+
+ public FakeMediaPeriodWithSelectTracksPosition(
+ TrackGroupArray trackGroupArray, EventDispatcher eventDispatcher) {
+ super(trackGroupArray, eventDispatcher);
+ selectTracksPositionUs = C.TIME_UNSET;
+ }
+
+ @Override
+ public long selectTracks(
+ @NullableType TrackSelection[] selections,
+ boolean[] mayRetainStreamFlags,
+ @NullableType SampleStream[] streams,
+ boolean[] streamResetFlags,
+ long positionUs) {
+ selectTracksPositionUs = positionUs;
+ return super.selectTracks(
+ selections, mayRetainStreamFlags, streams, streamResetFlags, positionUs);
+ }
+ }
+
+ private static final class MergingPeriodDefinition {
+
+ public long timeOffsetUs;
+ public Format[] formats;
+
+ public MergingPeriodDefinition(long timeOffsetUs, Format... formats) {
+ this.timeOffsetUs = timeOffsetUs;
+ this.formats = formats;
+ }
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java
index 1434d28..4d91b7a 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java
@@ -37,7 +37,7 @@
public class MergingMediaSourceTest {
@Test
- public void testMergingDynamicTimelines() throws IOException {
+ public void mergingDynamicTimelines() throws IOException {
FakeTimeline firstTimeline =
new FakeTimeline(new TimelineWindowDefinition(true, true, C.TIME_UNSET));
FakeTimeline secondTimeline =
@@ -46,14 +46,14 @@
}
@Test
- public void testMergingStaticTimelines() throws IOException {
+ public void mergingStaticTimelines() throws IOException {
FakeTimeline firstTimeline = new FakeTimeline(new TimelineWindowDefinition(true, false, 20));
FakeTimeline secondTimeline = new FakeTimeline(new TimelineWindowDefinition(true, false, 10));
testMergingMediaSourcePrepare(firstTimeline, secondTimeline);
}
@Test
- public void testMergingTimelinesWithDifferentPeriodCounts() throws IOException {
+ public void mergingTimelinesWithDifferentPeriodCounts() throws IOException {
FakeTimeline firstTimeline = new FakeTimeline(new TimelineWindowDefinition(1, null));
FakeTimeline secondTimeline = new FakeTimeline(new TimelineWindowDefinition(2, null));
try {
@@ -65,7 +65,7 @@
}
@Test
- public void testMergingMediaSourcePeriodCreation() throws Exception {
+ public void mergingMediaSourcePeriodCreation() throws Exception {
FakeMediaSource[] mediaSources = new FakeMediaSource[2];
for (int i = 0; i < mediaSources.length; i++) {
mediaSources[i] = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 2));
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
index eaae4d9..9a45248 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
@@ -15,12 +15,15 @@
*/
package com.google.android.exoplayer2.source;
+import static com.google.android.exoplayer2.C.BUFFER_FLAG_ENCRYPTED;
+import static com.google.android.exoplayer2.C.BUFFER_FLAG_KEY_FRAME;
import static com.google.android.exoplayer2.C.RESULT_BUFFER_READ;
import static com.google.android.exoplayer2.C.RESULT_FORMAT_READ;
import static com.google.android.exoplayer2.C.RESULT_NOTHING_READ;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Long.MIN_VALUE;
import static java.util.Arrays.copyOfRange;
+import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.when;
import androidx.annotation.Nullable;
@@ -32,14 +35,15 @@
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -54,17 +58,12 @@
private static final int ALLOCATION_SIZE = 16;
- private static final Format FORMAT_1 = Format.createSampleFormat("1", "mimeType", 0);
- private static final Format FORMAT_2 = Format.createSampleFormat("2", "mimeType", 0);
- private static final Format FORMAT_1_COPY = Format.createSampleFormat("1", "mimeType", 0);
- private static final Format FORMAT_SPLICED = Format.createSampleFormat("spliced", "mimeType", 0);
+ private static final Format FORMAT_1 = buildFormat(/* id= */ "1");
+ private static final Format FORMAT_2 = buildFormat(/* id= */ "2");
+ private static final Format FORMAT_1_COPY = buildFormat(/* id= */ "1");
+ private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
private static final Format FORMAT_ENCRYPTED =
- Format.createSampleFormat(
- /* id= */ "encrypted",
- "mimeType",
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- new DrmInitData());
+ new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/*
@@ -112,44 +111,41 @@
C.BUFFER_FLAG_KEY_FRAME, C.BUFFER_FLAG_ENCRYPTED, 0, C.BUFFER_FLAG_ENCRYPTED,
};
private static final long[] ENCRYPTED_SAMPLE_TIMESTAMPS = new long[] {0, 1000, 2000, 3000};
- private static final Format[] ENCRYPTED_SAMPLES_FORMATS =
+ private static final Format[] ENCRYPTED_SAMPLE_FORMATS =
new Format[] {FORMAT_ENCRYPTED, FORMAT_ENCRYPTED, FORMAT_1, FORMAT_ENCRYPTED};
/** Encrypted samples require the encryption preamble. */
- private static final int[] ENCRYPTED_SAMPLES_SIZES = new int[] {1, 3, 1, 3};
+ private static final int[] ENCRYPTED_SAMPLE_SIZES = new int[] {1, 3, 1, 3};
- private static final int[] ENCRYPTED_SAMPLES_OFFSETS = new int[] {7, 4, 3, 0};
- private static final byte[] ENCRYPTED_SAMPLES_DATA = new byte[8];
-
- static {
- Arrays.fill(ENCRYPTED_SAMPLES_DATA, (byte) 1);
- }
+ private static final int[] ENCRYPTED_SAMPLE_OFFSETS = new int[] {7, 4, 3, 0};
+ private static final byte[] ENCRYPTED_SAMPLE_DATA = new byte[] {1, 1, 1, 1, 1, 1, 1, 1};
private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA =
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private Allocator allocator;
- private DrmSessionManager<ExoMediaCrypto> mockDrmSessionManager;
- private DrmSession<ExoMediaCrypto> mockDrmSession;
+ private DrmSessionManager mockDrmSessionManager;
+ private DrmSession mockDrmSession;
+ private MediaSourceEventDispatcher eventDispatcher;
private SampleQueue sampleQueue;
private FormatHolder formatHolder;
private DecoderInputBuffer inputBuffer;
@Before
- @SuppressWarnings("unchecked")
- public void setUp() throws Exception {
+ public void setUp() {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
- mockDrmSessionManager =
- (DrmSessionManager<ExoMediaCrypto>) Mockito.mock(DrmSessionManager.class);
- mockDrmSession = (DrmSession<ExoMediaCrypto>) Mockito.mock(DrmSession.class);
- when(mockDrmSessionManager.acquireSession(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ mockDrmSessionManager = Mockito.mock(DrmSessionManager.class);
+ mockDrmSession = Mockito.mock(DrmSession.class);
+ when(mockDrmSessionManager.acquireSession(
+ ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(mockDrmSession);
- sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
+ eventDispatcher = new MediaSourceEventDispatcher();
+ sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
formatHolder = new FormatHolder();
inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
allocator = null;
sampleQueue = null;
formatHolder = null;
@@ -157,7 +153,7 @@
}
@Test
- public void testCapacityIncreases() {
+ public void capacityIncreases() {
int numberOfSamplesToInput = 3 * SampleQueue.SAMPLE_CAPACITY_INCREMENT + 1;
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(
@@ -185,7 +181,7 @@
}
@Test
- public void testResetReleasesAllocations() {
+ public void resetReleasesAllocations() {
writeTestData();
assertAllocationCount(10);
sampleQueue.reset();
@@ -193,12 +189,12 @@
}
@Test
- public void testReadWithoutWrite() {
+ public void readWithoutWrite() {
assertNoSamplesToRead(null);
}
@Test
- public void testEqualFormatsDeduplicated() {
+ public void equalFormatsDeduplicated() {
sampleQueue.format(FORMAT_1);
assertReadFormat(false, FORMAT_1);
// If the same format is written then it should not cause a format change on the read side.
@@ -210,7 +206,7 @@
}
@Test
- public void testMultipleFormatsDeduplicated() {
+ public void multipleFormatsDeduplicated() {
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
@@ -237,7 +233,7 @@
}
@Test
- public void testReadSingleSamples() {
+ public void readSingleSamples() {
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
assertAllocationCount(1);
@@ -296,7 +292,7 @@
}
@Test
- public void testReadMultiSamples() {
+ public void readMultiSamples() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
assertAllocationCount(10);
@@ -307,7 +303,7 @@
}
@Test
- public void testReadMultiSamplesTwice() {
+ public void readMultiSamplesTwice() {
writeTestData();
writeTestData();
assertAllocationCount(20);
@@ -319,7 +315,7 @@
}
@Test
- public void testReadMultiWithSeek() {
+ public void readMultiWithSeek() {
writeTestData();
assertReadTestData();
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
@@ -335,20 +331,20 @@
}
@Test
- public void testEmptyQueueReturnsLoadingFinished() {
+ public void emptyQueueReturnsLoadingFinished() {
sampleQueue.sampleData(new ParsableByteArray(DATA), DATA.length);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
assertThat(sampleQueue.isReady(/* loadingFinished= */ true)).isTrue();
}
@Test
- public void testIsReadyWithUpstreamFormatOnlyReturnsTrue() {
+ public void isReadyWithUpstreamFormatOnlyReturnsTrue() {
sampleQueue.format(FORMAT_ENCRYPTED);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
}
@Test
- public void testIsReadyReturnsTrueForValidDrmSession() {
+ public void isReadyReturnsTrueForValidDrmSession() {
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
@@ -357,27 +353,29 @@
}
@Test
- public void testIsReadyReturnsTrueForClearSampleAndPlayClearSamplesWithoutKeysIsTrue() {
+ public void isReadyReturnsTrueForClearSampleAndPlayClearSamplesWithoutKeysIsTrue() {
when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true);
// We recreate the queue to ensure the mock DRM session manager flags are taken into account.
- sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
+ sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
writeTestDataWithEncryptedSections();
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
}
@Test
- public void testReadEncryptedSectionsWaitsForKeys() {
+ public void readEncryptedSectionsWaitsForKeys() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadNothing(/* formatRequired= */ false);
+ assertThat(inputBuffer.waitingForKeys).isTrue();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertReadEncryptedSample(/* sampleIndex= */ 0);
+ assertThat(inputBuffer.waitingForKeys).isFalse();
}
@Test
- public void testReadEncryptedSectionsPopulatesDrmSession() {
+ public void readEncryptedSectionsPopulatesDrmSession() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
writeTestDataWithEncryptedSections();
@@ -416,11 +414,9 @@
}
@Test
- @SuppressWarnings("unchecked")
- public void testAllowPlaceholderSessionPopulatesDrmSession() {
+ public void allowPlaceholderSessionPopulatesDrmSession() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
- DrmSession<ExoMediaCrypto> mockPlaceholderDrmSession =
- (DrmSession<ExoMediaCrypto>) Mockito.mock(DrmSession.class);
+ DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
@@ -459,10 +455,62 @@
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
+ assertReadEncryptedSample(/* sampleIndex= */ 3);
}
@Test
- public void testReadWithErrorSessionReadsNothingAndThrows() throws IOException {
+ public void trailingCryptoInfoInitializationVectorBytesZeroed() {
+ when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
+ when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ when(mockDrmSessionManager.acquirePlaceholderSession(
+ ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
+ .thenReturn(mockPlaceholderDrmSession);
+
+ writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
+ byte[] sampleData = new byte[] {0, 1, 2};
+ byte[] initializationVector = new byte[] {7, 6, 5, 4, 3, 2, 1, 0};
+ byte[] encryptedSampleData =
+ TestUtil.joinByteArrays(
+ new byte[] {
+ 0x08, // subsampleEncryption = false (1 bit), ivSize = 8 (7 bits).
+ },
+ initializationVector,
+ sampleData);
+ writeSample(
+ encryptedSampleData, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME | BUFFER_FLAG_ENCRYPTED);
+
+ int result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+
+ // Fill cryptoInfo.iv with non-zero data. When the 8 byte initialization vector is written into
+ // it, we expect the trailing 8 bytes to be zeroed.
+ inputBuffer.cryptoInfo.iv = new byte[16];
+ Arrays.fill(inputBuffer.cryptoInfo.iv, (byte) 1);
+
+ result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_BUFFER_READ);
+
+ // Assert cryptoInfo.iv contains the 8-byte initialization vector and that the trailing 8 bytes
+ // have been zeroed.
+ byte[] expectedInitializationVector = Arrays.copyOf(initializationVector, 16);
+ assertArrayEquals(expectedInitializationVector, inputBuffer.cryptoInfo.iv);
+ }
+
+ @Test
+ public void readWithErrorSessionReadsNothingAndThrows() throws IOException {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
@@ -483,10 +531,10 @@
}
@Test
- public void testAllowPlayClearSamplesWithoutKeysReadsClearSamples() {
+ public void allowPlayClearSamplesWithoutKeysReadsClearSamples() {
when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true);
// We recreate the queue to ensure the mock DRM session manager flags are taken into account.
- sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
+ sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
@@ -495,7 +543,7 @@
}
@Test
- public void testSeekAfterDiscard() {
+ public void seekAfterDiscard() {
writeTestData();
assertReadTestData();
sampleQueue.discardToRead();
@@ -512,7 +560,7 @@
}
@Test
- public void testAdvanceToEnd() {
+ public void advanceToEnd() {
writeTestData();
sampleQueue.advanceToEnd();
assertAllocationCount(10);
@@ -526,7 +574,7 @@
}
@Test
- public void testAdvanceToEndRetainsUnassignedData() {
+ public void advanceToEndRetainsUnassignedData() {
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.advanceToEnd();
@@ -550,7 +598,7 @@
}
@Test
- public void testAdvanceToBeforeBuffer() {
+ public void advanceToBeforeBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(SAMPLE_TIMESTAMPS[0] - 1);
// Should have no effect (we're already at the first frame).
@@ -560,7 +608,7 @@
}
@Test
- public void testAdvanceToStartOfBuffer() {
+ public void advanceToStartOfBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(SAMPLE_TIMESTAMPS[0]);
// Should have no effect (we're already at the first frame).
@@ -570,7 +618,7 @@
}
@Test
- public void testAdvanceToEndOfBuffer() {
+ public void advanceToEndOfBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP);
// Should advance to 2nd keyframe (the 4th frame).
@@ -580,7 +628,7 @@
}
@Test
- public void testAdvanceToAfterBuffer() {
+ public void advanceToAfterBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1);
// Should advance to 2nd keyframe (the 4th frame).
@@ -590,7 +638,7 @@
}
@Test
- public void testSeekToBeforeBuffer() {
+ public void seekToBeforeBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0] - 1, false);
assertThat(success).isFalse();
@@ -600,7 +648,7 @@
}
@Test
- public void testSeekToStartOfBuffer() {
+ public void seekToStartOfBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], false);
assertThat(success).isTrue();
@@ -610,7 +658,7 @@
}
@Test
- public void testSeekToEndOfBuffer() {
+ public void seekToEndOfBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, false);
assertThat(success).isTrue();
@@ -620,7 +668,7 @@
}
@Test
- public void testSeekToAfterBuffer() {
+ public void seekToAfterBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, false);
assertThat(success).isFalse();
@@ -630,7 +678,7 @@
}
@Test
- public void testSeekToAfterBufferAllowed() {
+ public void seekToAfterBufferAllowed() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, true);
assertThat(success).isTrue();
@@ -640,7 +688,7 @@
}
@Test
- public void testSeekToEndAndBackToStart() {
+ public void seekToEndAndBackToStart() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, false);
assertThat(success).isTrue();
@@ -656,7 +704,7 @@
}
@Test
- public void testDiscardToEnd() {
+ public void discardToEnd() {
writeTestData();
// Should discard everything.
sampleQueue.discardToEnd();
@@ -671,7 +719,7 @@
}
@Test
- public void testDiscardToStopAtReadPosition() {
+ public void discardToStopAtReadPosition() {
writeTestData();
// Shouldn't discard anything.
sampleQueue.discardTo(LAST_SAMPLE_TIMESTAMP, false, true);
@@ -712,7 +760,7 @@
}
@Test
- public void testDiscardToDontStopAtReadPosition() {
+ public void discardToDontStopAtReadPosition() {
writeTestData();
// Shouldn't discard anything.
sampleQueue.discardTo(SAMPLE_TIMESTAMPS[1] - 1, false, false);
@@ -729,7 +777,7 @@
}
@Test
- public void testDiscardUpstream() {
+ public void discardUpstream() {
writeTestData();
sampleQueue.discardUpstreamSamples(8);
assertAllocationCount(10);
@@ -754,7 +802,7 @@
}
@Test
- public void testDiscardUpstreamMulti() {
+ public void discardUpstreamMulti() {
writeTestData();
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(4);
@@ -765,7 +813,7 @@
}
@Test
- public void testDiscardUpstreamBeforeRead() {
+ public void discardUpstreamBeforeRead() {
writeTestData();
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(4);
@@ -775,7 +823,7 @@
}
@Test
- public void testDiscardUpstreamAfterRead() {
+ public void discardUpstreamAfterRead() {
writeTestData();
assertReadTestData(null, 0, 3);
sampleQueue.discardUpstreamSamples(8);
@@ -797,7 +845,7 @@
}
@Test
- public void testLargestQueuedTimestampWithDiscardUpstream() {
+ public void largestQueuedTimestampWithDiscardUpstream() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
sampleQueue.discardUpstreamSamples(SAMPLE_TIMESTAMPS.length - 1);
@@ -810,7 +858,7 @@
}
@Test
- public void testLargestQueuedTimestampWithDiscardUpstreamDecodeOrder() {
+ public void largestQueuedTimestampWithDiscardUpstreamDecodeOrder() {
long[] decodeOrderTimestamps = new long[] {0, 3000, 2000, 1000, 4000, 7000, 6000, 5000};
writeTestData(
DATA, SAMPLE_SIZES, SAMPLE_OFFSETS, decodeOrderTimestamps, SAMPLE_FORMATS, SAMPLE_FLAGS);
@@ -826,9 +874,9 @@
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE);
}
-
+
@Test
- public void testLargestQueuedTimestampWithRead() {
+ public void largestQueuedTimestampWithRead() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
assertReadTestData();
@@ -837,21 +885,104 @@
}
@Test
- public void testSetSampleOffset() {
+ public void setSampleOffsetBeforeData() {
long sampleOffsetUs = 1000;
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
writeTestData();
- assertReadTestData(null, 0, 8, sampleOffsetUs);
- assertReadEndOfStream(false);
+ assertReadTestData(
+ /* startFormat= */ null, /* firstSampleIndex= */ 0, /* sampleCount= */ 8, sampleOffsetUs);
+ assertReadEndOfStream(/* formatRequired= */ false);
}
@Test
- public void testSplice() {
+ public void setSampleOffsetBetweenSamples() {
+ writeTestData();
+ long sampleOffsetUs = 1000;
+ sampleQueue.setSampleOffsetUs(sampleOffsetUs);
+
+ // Write a final sample now the offset is set.
+ long unadjustedTimestampUs = LAST_SAMPLE_TIMESTAMP + 1234;
+ writeSample(DATA, unadjustedTimestampUs, /* sampleFlags= */ 0);
+
+ assertReadTestData();
+ // We expect to read the format adjusted to account for the sample offset, followed by the final
+ // sample and then the end of stream.
+ assertReadFormat(
+ /* formatRequired= */ false,
+ FORMAT_2.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build());
+ assertReadSample(
+ unadjustedTimestampUs + sampleOffsetUs,
+ /* isKeyFrame= */ false,
+ /* isEncrypted= */ false,
+ DATA,
+ /* offset= */ 0,
+ DATA.length);
+ assertReadEndOfStream(/* formatRequired= */ false);
+ }
+
+ @Test
+ public void adjustUpstreamFormat() {
+ String label = "label";
+ sampleQueue =
+ new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) {
+ @Override
+ public Format getAdjustedUpstreamFormat(Format format) {
+ return super.getAdjustedUpstreamFormat(copyWithLabel(format, label));
+ }
+ };
+
+ writeFormat(FORMAT_1);
+ assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, label));
+ assertReadEndOfStream(/* formatRequired= */ false);
+ }
+
+ @Test
+ public void invalidateUpstreamFormatAdjustment() {
+ AtomicReference<String> label = new AtomicReference<>("label1");
+ sampleQueue =
+ new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) {
+ @Override
+ public Format getAdjustedUpstreamFormat(Format format) {
+ return super.getAdjustedUpstreamFormat(copyWithLabel(format, label.get()));
+ }
+ };
+
+ writeFormat(FORMAT_1);
+ writeSample(DATA, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME);
+
+ // Make a change that'll affect the SampleQueue's format adjustment, and invalidate it.
+ label.set("label2");
+ sampleQueue.invalidateUpstreamFormatAdjustment();
+
+ writeSample(DATA, /* timestampUs= */ 1, /* sampleFlags= */ 0);
+
+ assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, "label1"));
+ assertReadSample(
+ /* timeUs= */ 0,
+ /* isKeyFrame= */ true,
+ /* isEncrypted= */ false,
+ DATA,
+ /* offset= */ 0,
+ DATA.length);
+ assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, "label2"));
+ assertReadSample(
+ /* timeUs= */ 1,
+ /* isKeyFrame= */ false,
+ /* isEncrypted= */ false,
+ DATA,
+ /* offset= */ 0,
+ DATA.length);
+ assertReadEndOfStream(/* formatRequired= */ false);
+ }
+
+ @Test
+ public void splice() {
writeTestData();
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[4];
- writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
+ writeFormat(FORMAT_SPLICED);
+ writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4);
assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
@@ -859,13 +990,14 @@
}
@Test
- public void testSpliceAfterRead() {
+ public void spliceAfterRead() {
writeTestData();
assertReadTestData(null, 0, 4);
sampleQueue.splice();
// Splice should fail, leaving the last 4 samples unchanged.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3];
- writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
+ writeFormat(FORMAT_SPLICED);
+ writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(SAMPLE_FORMATS[3], 4, 4);
assertReadEndOfStream(false);
@@ -874,23 +1006,26 @@
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written
spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3] + 1;
- writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
+ writeFormat(FORMAT_SPLICED);
+ writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@Test
- public void testSpliceWithSampleOffset() {
+ public void spliceWithSampleOffset() {
long sampleOffsetUs = 30000;
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
writeTestData();
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[4];
- writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
+ writeFormat(FORMAT_SPLICED);
+ writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4, sampleOffsetUs);
- assertReadFormat(false, FORMAT_SPLICED.copyWithSubsampleOffsetUs(sampleOffsetUs));
+ assertReadFormat(
+ false, FORMAT_SPLICED.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build());
assertReadSample(
spliceSampleTimeUs + sampleOffsetUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
@@ -908,11 +1043,11 @@
private void writeTestDataWithEncryptedSections() {
writeTestData(
- ENCRYPTED_SAMPLES_DATA,
- ENCRYPTED_SAMPLES_SIZES,
- ENCRYPTED_SAMPLES_OFFSETS,
+ ENCRYPTED_SAMPLE_DATA,
+ ENCRYPTED_SAMPLE_SIZES,
+ ENCRYPTED_SAMPLE_OFFSETS,
ENCRYPTED_SAMPLE_TIMESTAMPS,
- ENCRYPTED_SAMPLES_FORMATS,
+ ENCRYPTED_SAMPLE_FORMATS,
ENCRYPTED_SAMPLES_FLAGS);
}
@@ -938,11 +1073,20 @@
}
}
- /** Writes a single sample to {@code sampleQueue}. */
- private void writeSample(byte[] data, long timestampUs, Format format, int sampleFlags) {
+ /** Writes a {@link Format} to the {@code sampleQueue}. */
+ private void writeFormat(Format format) {
sampleQueue.format(format);
+ }
+
+ /** Writes a single sample to {@code sampleQueue}. */
+ private void writeSample(byte[] data, long timestampUs, int sampleFlags) {
sampleQueue.sampleData(new ParsableByteArray(data), data.length);
- sampleQueue.sampleMetadata(timestampUs, sampleFlags, data.length, 0, null);
+ sampleQueue.sampleMetadata(
+ timestampUs,
+ sampleFlags,
+ data.length,
+ /* offset= */ 0,
+ (sampleFlags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null);
}
/**
@@ -1115,7 +1259,7 @@
}
private void assertReadEncryptedSample(int sampleIndex) {
- byte[] sampleData = new byte[ENCRYPTED_SAMPLES_SIZES[sampleIndex]];
+ byte[] sampleData = new byte[ENCRYPTED_SAMPLE_SIZES[sampleIndex]];
Arrays.fill(sampleData, (byte) 1);
boolean isKeyFrame = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0;
boolean isEncrypted = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
@@ -1125,7 +1269,7 @@
isEncrypted,
sampleData,
/* offset= */ 0,
- ENCRYPTED_SAMPLES_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
+ ENCRYPTED_SAMPLE_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
}
/**
@@ -1203,6 +1347,14 @@
private static Format adjustFormat(@Nullable Format format, long sampleOffsetUs) {
return format == null || sampleOffsetUs == 0
? format
- : format.copyWithSubsampleOffsetUs(sampleOffsetUs);
+ : format.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build();
+ }
+
+ private static Format buildFormat(String id) {
+ return new Format.Builder().setId(id).setSubsampleOffsetUs(0).build();
+ }
+
+ private static Format copyWithLabel(Format format, String label) {
+ return format.buildUpon().setLabel(label).build();
}
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java
index 17b0996..bfa4dbd 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java
@@ -32,7 +32,7 @@
public static final long RANDOM_SEED = 1234567890L;
@Test
- public void testDefaultShuffleOrder() {
+ public void defaultShuffleOrder() {
assertShuffleOrderCorrectness(new DefaultShuffleOrder(0, RANDOM_SEED), 0);
assertShuffleOrderCorrectness(new DefaultShuffleOrder(1, RANDOM_SEED), 1);
assertShuffleOrderCorrectness(new DefaultShuffleOrder(5, RANDOM_SEED), 5);
@@ -55,7 +55,7 @@
}
@Test
- public void testDefaultShuffleOrderSideloaded() {
+ public void defaultShuffleOrderSideloaded() {
int[] shuffledIndices = new int[] {2, 1, 0, 4, 3};
ShuffleOrder shuffleOrder = new DefaultShuffleOrder(shuffledIndices, RANDOM_SEED);
assertThat(shuffleOrder.getFirstIndex()).isEqualTo(2);
@@ -72,7 +72,7 @@
}
@Test
- public void testUnshuffledShuffleOrder() {
+ public void unshuffledShuffleOrder() {
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(0), 0);
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(1), 1);
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(5), 5);
@@ -95,7 +95,7 @@
}
@Test
- public void testUnshuffledShuffleOrderIsUnshuffled() {
+ public void unshuffledShuffleOrderIsUnshuffled() {
ShuffleOrder shuffleOrder = new UnshuffledShuffleOrder(5);
assertThat(shuffleOrder.getFirstIndex()).isEqualTo(0);
assertThat(shuffleOrder.getLastIndex()).isEqualTo(4);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java
index 6ff4f78..fe4255c 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java
@@ -40,7 +40,7 @@
}
@Test
- public void testGetPeriodPositionDynamicWindowUnknownDuration() {
+ public void getPeriodPositionDynamicWindowUnknownDuration() {
SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
@@ -54,7 +54,7 @@
}
@Test
- public void testGetPeriodPositionDynamicWindowKnownDuration() {
+ public void getPeriodPositionDynamicWindowKnownDuration() {
long windowDurationUs = 1000;
SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java
index 36adc97..75653e8 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupArrayTest.java
@@ -29,10 +29,11 @@
public final class TrackGroupArrayTest {
@Test
- public void testParcelable() {
- Format format1 = Format.createSampleFormat("1", MimeTypes.VIDEO_H264, 0);
- Format format2 = Format.createSampleFormat("2", MimeTypes.AUDIO_AAC, 0);
- Format format3 = Format.createSampleFormat("3", MimeTypes.VIDEO_H264, 0);
+ public void parcelable() {
+ Format.Builder formatBuilder = new Format.Builder();
+ Format format1 = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build();
+ Format format2 = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_AAC).build();
+ Format format3 = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build();
TrackGroup trackGroup1 = new TrackGroup(format1, format2);
TrackGroup trackGroup2 = new TrackGroup(format3);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java
index 4de1f8e..ba42463 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/TrackGroupTest.java
@@ -29,9 +29,10 @@
public final class TrackGroupTest {
@Test
- public void testParcelable() {
- Format format1 = Format.createSampleFormat("1", MimeTypes.VIDEO_H264, 0);
- Format format2 = Format.createSampleFormat("2", MimeTypes.AUDIO_AAC, 0);
+ public void parcelable() {
+ Format.Builder formatBuilder = new Format.Builder();
+ Format format1 = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build();
+ Format format2 = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_AAC).build();
TrackGroup trackGroupToParcel = new TrackGroup(format1, format2);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java
index 0cd27a9..5b7713a 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java
@@ -40,14 +40,14 @@
}
@Test
- public void testSetAdCount() {
+ public void setAdCount() {
assertThat(state.adGroups[0].count).isEqualTo(C.LENGTH_UNSET);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
assertThat(state.adGroups[0].count).isEqualTo(1);
}
@Test
- public void testSetAdUriBeforeAdCount() {
+ public void setAdUriBeforeAdCount() {
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2);
@@ -58,7 +58,7 @@
}
@Test
- public void testSetAdErrorBeforeAdCount() {
+ public void setAdErrorBeforeAdCount() {
state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2);
@@ -68,7 +68,7 @@
}
@Test
- public void testGetFirstAdIndexToPlayIsZero() {
+ public void getFirstAdIndexToPlayIsZero() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI);
@@ -77,7 +77,7 @@
}
@Test
- public void testGetFirstAdIndexToPlaySkipsPlayedAd() {
+ public void getFirstAdIndexToPlaySkipsPlayedAd() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI);
@@ -90,7 +90,7 @@
}
@Test
- public void testGetFirstAdIndexToPlaySkipsSkippedAd() {
+ public void getFirstAdIndexToPlaySkipsSkippedAd() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI);
@@ -103,7 +103,7 @@
}
@Test
- public void testGetFirstAdIndexToPlaySkipsErrorAds() {
+ public void getFirstAdIndexToPlaySkipsErrorAds() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI);
@@ -115,7 +115,7 @@
}
@Test
- public void testGetNextAdIndexToPlaySkipsErrorAds() {
+ public void getNextAdIndexToPlaySkipsErrorAds() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
@@ -125,7 +125,7 @@
}
@Test
- public void testSetAdStateTwiceThrows() {
+ public void setAdStateTwiceThrows() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
try {
@@ -137,7 +137,7 @@
}
@Test
- public void testSkipAllWithoutAdCount() {
+ public void skipAllWithoutAdCount() {
state = state.withSkippedAdGroup(0);
state = state.withSkippedAdGroup(1);
assertThat(state.adGroups[0].count).isEqualTo(0);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/SpanUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/SpanUtilTest.java
deleted file mode 100644
index 3a71925..0000000
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/SpanUtilTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.text;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.graphics.Color;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.style.BackgroundColorSpan;
-import android.text.style.ForegroundColorSpan;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link SpanUtil}. */
-@RunWith(AndroidJUnit4.class)
-public class SpanUtilTest {
-
- @Test
- public void addOrReplaceSpan_replacesSameTypeAndIndexes() {
- Spannable spannable = SpannableString.valueOf("test text");
- spannable.setSpan(
- new ForegroundColorSpan(Color.CYAN),
- /* start= */ 2,
- /* end= */ 5,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- ForegroundColorSpan newSpan = new ForegroundColorSpan(Color.BLUE);
- SpanUtil.addOrReplaceSpan(
- spannable, newSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
- assertThat(spans).asList().containsExactly(newSpan);
- }
-
- @Test
- public void addOrReplaceSpan_ignoresDifferentType() {
- Spannable spannable = SpannableString.valueOf("test text");
- ForegroundColorSpan originalSpan = new ForegroundColorSpan(Color.CYAN);
- spannable.setSpan(originalSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- BackgroundColorSpan newSpan = new BackgroundColorSpan(Color.BLUE);
- SpanUtil.addOrReplaceSpan(spannable, newSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
- assertThat(spans).asList().containsExactly(originalSpan, newSpan).inOrder();
- }
-
- @Test
- public void addOrReplaceSpan_ignoresDifferentStartEndAndFlags() {
- Spannable spannable = SpannableString.valueOf("test text");
- ForegroundColorSpan originalSpan = new ForegroundColorSpan(Color.CYAN);
- spannable.setSpan(originalSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- ForegroundColorSpan differentStart = new ForegroundColorSpan(Color.GREEN);
- SpanUtil.addOrReplaceSpan(
- spannable, differentStart, /* start= */ 3, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- ForegroundColorSpan differentEnd = new ForegroundColorSpan(Color.BLUE);
- SpanUtil.addOrReplaceSpan(
- spannable, differentEnd, /* start= */ 2, /* end= */ 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- ForegroundColorSpan differentFlags = new ForegroundColorSpan(Color.GREEN);
- SpanUtil.addOrReplaceSpan(
- spannable, differentFlags, /* start= */ 2, /* end= */ 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-
- Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
- assertThat(spans)
- .asList()
- .containsExactly(originalSpan, differentStart, differentEnd, differentFlags)
- .inOrder();
- }
-}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/span/SpanUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/span/SpanUtilTest.java
new file mode 100644
index 0000000..cdccf04
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/span/SpanUtilTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.text.span;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SpanUtil}. */
+@RunWith(AndroidJUnit4.class)
+public class SpanUtilTest {
+
+ @Test
+ public void addOrReplaceSpan_replacesSameTypeAndIndexes() {
+ Spannable spannable = SpannableString.valueOf("test text");
+ spannable.setSpan(
+ new ForegroundColorSpan(Color.CYAN),
+ /* start= */ 2,
+ /* end= */ 5,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ ForegroundColorSpan newSpan = new ForegroundColorSpan(Color.BLUE);
+ SpanUtil.addOrReplaceSpan(
+ spannable, newSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
+ assertThat(spans).asList().containsExactly(newSpan);
+ }
+
+ @Test
+ public void addOrReplaceSpan_ignoresDifferentType() {
+ Spannable spannable = SpannableString.valueOf("test text");
+ ForegroundColorSpan originalSpan = new ForegroundColorSpan(Color.CYAN);
+ spannable.setSpan(originalSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ BackgroundColorSpan newSpan = new BackgroundColorSpan(Color.BLUE);
+ SpanUtil.addOrReplaceSpan(spannable, newSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
+ assertThat(spans).asList().containsExactly(originalSpan, newSpan).inOrder();
+ }
+
+ @Test
+ public void addOrReplaceSpan_ignoresDifferentStartEndAndFlags() {
+ Spannable spannable = SpannableString.valueOf("test text");
+ ForegroundColorSpan originalSpan = new ForegroundColorSpan(Color.CYAN);
+ spannable.setSpan(originalSpan, /* start= */ 2, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ ForegroundColorSpan differentStart = new ForegroundColorSpan(Color.GREEN);
+ SpanUtil.addOrReplaceSpan(
+ spannable, differentStart, /* start= */ 3, /* end= */ 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ForegroundColorSpan differentEnd = new ForegroundColorSpan(Color.BLUE);
+ SpanUtil.addOrReplaceSpan(
+ spannable, differentEnd, /* start= */ 2, /* end= */ 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ForegroundColorSpan differentFlags = new ForegroundColorSpan(Color.GREEN);
+ SpanUtil.addOrReplaceSpan(
+ spannable, differentFlags, /* start= */ 2, /* end= */ 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
+ assertThat(spans)
+ .asList()
+ .containsExactly(originalSpan, differentStart, differentEnd, differentFlags)
+ .inOrder();
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java
index 65536f2..379e189 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java
@@ -46,7 +46,7 @@
private static final String POSITIONS_WITHOUT_PLAYRES = "ssa/positioning_without_playres";
@Test
- public void testDecodeEmpty() throws IOException {
+ public void decodeEmpty() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -56,7 +56,7 @@
}
@Test
- public void testDecodeTypical() throws IOException {
+ public void decodeTypical() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -81,7 +81,7 @@
}
@Test
- public void testDecodeTypicalWithInitializationData() throws IOException {
+ public void decodeTypicalWithInitializationData() throws IOException {
byte[] headerBytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_HEADER_ONLY);
byte[] formatBytes =
@@ -101,7 +101,7 @@
}
@Test
- public void testDecodeOverlappingTimecodes() throws IOException {
+ public void decodeOverlappingTimecodes() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), OVERLAPPING_TIMECODES);
@@ -151,7 +151,7 @@
}
@Test
- public void testDecodePositions() throws IOException {
+ public void decodePositions() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), POSITIONS);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -204,7 +204,7 @@
}
@Test
- public void testDecodeInvalidPositions() throws IOException {
+ public void decodeInvalidPositions() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INVALID_POSITIONS);
@@ -240,7 +240,7 @@
}
@Test
- public void testDecodePositionsWithMissingPlayResY() throws IOException {
+ public void decodePositionsWithMissingPlayResY() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(
@@ -256,7 +256,7 @@
}
@Test
- public void testDecodeInvalidTimecodes() throws IOException {
+ public void decodeInvalidTimecodes() throws IOException {
// Parsing should succeed, parsing the third cue only.
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java
index 9f66f65..e233d8d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java
@@ -39,9 +39,10 @@
private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps";
private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end";
private static final String TYPICAL_WITH_TAGS = "subrip/typical_with_tags";
+ private static final String TYPICAL_NO_HOURS_AND_MILLIS = "subrip/typical_no_hours_and_millis";
@Test
- public void testDecodeEmpty() throws IOException {
+ public void decodeEmpty() throws IOException {
SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -51,7 +52,7 @@
}
@Test
- public void testDecodeTypical() throws IOException {
+ public void decodeTypical() throws IOException {
SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_FILE);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -63,7 +64,7 @@
}
@Test
- public void testDecodeTypicalWithByteOrderMark() throws IOException {
+ public void decodeTypicalWithByteOrderMark() throws IOException {
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
TestUtil.getByteArray(
@@ -77,7 +78,7 @@
}
@Test
- public void testDecodeTypicalExtraBlankLine() throws IOException {
+ public void decodeTypicalExtraBlankLine() throws IOException {
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
TestUtil.getByteArray(
@@ -91,7 +92,7 @@
}
@Test
- public void testDecodeTypicalMissingTimecode() throws IOException {
+ public void decodeTypicalMissingTimecode() throws IOException {
// Parsing should succeed, parsing the first and third cues only.
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
@@ -105,7 +106,7 @@
}
@Test
- public void testDecodeTypicalMissingSequence() throws IOException {
+ public void decodeTypicalMissingSequence() throws IOException {
// Parsing should succeed, parsing the first and third cues only.
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
@@ -119,7 +120,7 @@
}
@Test
- public void testDecodeTypicalNegativeTimestamps() throws IOException {
+ public void decodeTypicalNegativeTimestamps() throws IOException {
// Parsing should succeed, parsing the third cue only.
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
@@ -132,7 +133,7 @@
}
@Test
- public void testDecodeTypicalUnexpectedEnd() throws IOException {
+ public void decodeTypicalUnexpectedEnd() throws IOException {
// Parsing should succeed, parsing the first and second cues only.
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
@@ -145,15 +146,20 @@
}
@Test
- public void testDecodeCueWithTag() throws IOException {
+ public void decodeCueWithTag() throws IOException {
SubripDecoder decoder = new SubripDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TYPICAL_WITH_TAGS);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
- assertTypicalCue1(subtitle, 0);
- assertTypicalCue2(subtitle, 2);
- assertTypicalCue3(subtitle, 4);
+ assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString())
+ .isEqualTo("This is the first subtitle.");
+
+ assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString())
+ .isEqualTo("This is the second subtitle.\nSecond subtitle with second line.");
+
+ assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString())
+ .isEqualTo("This is the third subtitle.");
assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString())
.isEqualTo("This { \\an2} is not a valid tag due to the space after the opening bracket.");
@@ -172,6 +178,21 @@
assertAlignmentCue(subtitle, 26, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_END); // {/an9}
}
+ @Test
+ public void decodeTypicalNoHoursAndMillis() throws IOException {
+ SubripDecoder decoder = new SubripDecoder();
+ byte[] bytes =
+ TestUtil.getByteArray(
+ ApplicationProvider.getApplicationContext(), TYPICAL_NO_HOURS_AND_MILLIS);
+ Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
+
+ assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
+ assertTypicalCue1(subtitle, 0);
+ assertThat(subtitle.getEventTime(2)).isEqualTo(2_000_000);
+ assertThat(subtitle.getEventTime(3)).isEqualTo(3_000_000);
+ assertTypicalCue3(subtitle, 4);
+ }
+
private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) {
assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0);
assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString())
@@ -187,10 +208,12 @@
}
private static void assertTypicalCue3(Subtitle subtitle, int eventIndex) {
- assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(4567000);
+ long expectedStartTimeUs = (((2L * 60L * 60L) + 4L) * 1000L + 567L) * 1000L;
+ assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(expectedStartTimeUs);
assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString())
.isEqualTo("This is the third subtitle.");
- assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(8901000);
+ long expectedEndTimeUs = (((2L * 60L * 60L) + 8L) * 1000L + 901L) * 1000L;
+ assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(expectedEndTimeUs);
}
private static void assertAlignmentCue(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
index e06abcb..071d34e 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
@@ -27,6 +27,7 @@
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
+import com.google.android.exoplayer2.text.span.RubySpan;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ColorParser;
import java.io.IOException;
@@ -60,9 +61,11 @@
private static final String BITMAP_PIXEL_REGION_FILE = "ttml/bitmap_pixel_region.xml";
private static final String BITMAP_UNSUPPORTED_REGION_FILE = "ttml/bitmap_unsupported_region.xml";
private static final String VERTICAL_TEXT_FILE = "ttml/vertical_text.xml";
+ private static final String TEXT_COMBINE_FILE = "ttml/text_combine.xml";
+ private static final String RUBIES_FILE = "ttml/rubies.xml";
@Test
- public void testInlineAttributes() throws IOException, SubtitleDecoderException {
+ public void inlineAttributes() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -81,7 +84,7 @@
}
@Test
- public void testInheritInlineAttributes() throws IOException, SubtitleDecoderException {
+ public void inheritInlineAttributes() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -110,7 +113,7 @@
* @throws IOException thrown if reading subtitle file fails.
*/
@Test
- public void testLime() throws IOException, SubtitleDecoderException {
+ public void lime() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -125,7 +128,7 @@
}
@Test
- public void testInheritGlobalStyle() throws IOException, SubtitleDecoderException {
+ public void inheritGlobalStyle() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@@ -140,7 +143,7 @@
}
@Test
- public void testInheritGlobalStyleOverriddenByInlineAttributes()
+ public void inheritGlobalStyleOverriddenByInlineAttributes()
throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
@@ -174,7 +177,7 @@
}
@Test
- public void testInheritGlobalAndParent() throws IOException, SubtitleDecoderException {
+ public void inheritGlobalAndParent() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -213,7 +216,7 @@
}
@Test
- public void testInheritMultipleStyles() throws IOException, SubtitleDecoderException {
+ public void inheritMultipleStyles() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@@ -228,7 +231,7 @@
}
@Test
- public void testInheritMultipleStylesWithoutLocalAttributes()
+ public void inheritMultipleStylesWithoutLocalAttributes()
throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
@@ -250,8 +253,7 @@
}
@Test
- public void testMergeMultipleStylesWithParentStyle()
- throws IOException, SubtitleDecoderException {
+ public void mergeMultipleStylesWithParentStyle() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@@ -273,7 +275,7 @@
}
@Test
- public void testMultipleRegions() throws IOException, SubtitleDecoderException {
+ public void multipleRegions() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
List<Cue> cues = subtitle.getCues(1_000_000);
@@ -322,7 +324,7 @@
}
@Test
- public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
+ public void emptyStyleAttribute() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@@ -335,7 +337,7 @@
}
@Test
- public void testNonexistingStyleId() throws IOException, SubtitleDecoderException {
+ public void nonexistingStyleId() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@@ -348,7 +350,7 @@
}
@Test
- public void testNonExistingAndExistingStyleIdWithRedundantSpaces()
+ public void nonExistingAndExistingStyleIdWithRedundantSpaces()
throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
@@ -363,7 +365,7 @@
}
@Test
- public void testMultipleChaining() throws IOException, SubtitleDecoderException {
+ public void multipleChaining() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(CHAIN_MULTIPLE_STYLES_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@@ -387,7 +389,7 @@
}
@Test
- public void testNoUnderline() throws IOException, SubtitleDecoderException {
+ public void noUnderline() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -403,7 +405,7 @@
}
@Test
- public void testNoLinethrough() throws IOException, SubtitleDecoderException {
+ public void noLinethrough() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -419,7 +421,7 @@
}
@Test
- public void testFontSizeSpans() throws IOException, SubtitleDecoderException {
+ public void fontSizeSpans() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(10);
@@ -446,7 +448,7 @@
}
@Test
- public void testFontSizeWithMissingUnitIsIgnored() throws IOException, SubtitleDecoderException {
+ public void fontSizeWithMissingUnitIsIgnored() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_MISSING_UNIT_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@@ -458,7 +460,7 @@
}
@Test
- public void testFontSizeWithInvalidValueIsIgnored() throws IOException, SubtitleDecoderException {
+ public void fontSizeWithInvalidValueIsIgnored() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_INVALID_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
@@ -480,7 +482,7 @@
}
@Test
- public void testFontSizeWithEmptyValueIsIgnored() throws IOException, SubtitleDecoderException {
+ public void fontSizeWithEmptyValueIsIgnored() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_EMPTY_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(2);
@@ -492,7 +494,7 @@
}
@Test
- public void testFrameRate() throws IOException, SubtitleDecoderException {
+ public void frameRate() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(FRAME_RATE_TTML_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -503,7 +505,7 @@
}
@Test
- public void testBitmapPercentageRegion() throws IOException, SubtitleDecoderException {
+ public void bitmapPercentageRegion() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(BITMAP_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
@@ -532,7 +534,7 @@
}
@Test
- public void testBitmapPixelRegion() throws IOException, SubtitleDecoderException {
+ public void bitmapPixelRegion() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(BITMAP_PIXEL_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
@@ -553,7 +555,7 @@
}
@Test
- public void testBitmapUnsupportedRegion() throws IOException, SubtitleDecoderException {
+ public void bitmapUnsupportedRegion() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(BITMAP_UNSUPPORTED_REGION_FILE);
Cue cue = getOnlyCueAtTimeUs(subtitle, 1_000_000);
@@ -574,7 +576,7 @@
}
@Test
- public void testVerticalText() throws IOException, SubtitleDecoderException {
+ public void verticalText() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
@@ -587,6 +589,56 @@
assertThat(thirdCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
}
+ @Test
+ public void textCombine() throws IOException, SubtitleDecoderException {
+ TtmlSubtitle subtitle = getSubtitle(TEXT_COMBINE_FILE);
+
+ Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
+ assertThat(firstCue)
+ .hasHorizontalTextInVerticalContextSpanBetween(
+ "text with ".length(), "text with combined".length());
+
+ Spanned secondCue = getOnlyCueTextAtTimeUs(subtitle, 20_000_000);
+ assertThat(secondCue)
+ .hasNoHorizontalTextInVerticalContextSpanBetween(
+ "text with ".length(), "text with un-combined".length());
+
+ Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
+ assertThat(thirdCue).hasNoHorizontalTextInVerticalContextSpanBetween(0, thirdCue.length());
+ }
+
+ @Test
+ public void rubies() throws IOException, SubtitleDecoderException {
+ TtmlSubtitle subtitle = getSubtitle(RUBIES_FILE);
+
+ Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
+ assertThat(firstCue.toString()).isEqualTo("Cue with annotated text.");
+ assertThat(firstCue)
+ .hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
+ .withTextAndPosition("1st rubies", RubySpan.POSITION_OVER);
+ assertThat(firstCue)
+ .hasRubySpanBetween("Cue with annotated ".length(), "Cue with annotated text".length())
+ .withTextAndPosition("2nd rubies", RubySpan.POSITION_UNKNOWN);
+
+ Spanned secondCue = getOnlyCueTextAtTimeUs(subtitle, 20_000_000);
+ assertThat(secondCue.toString()).isEqualTo("Cue with annotated text.");
+ assertThat(secondCue)
+ .hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
+ .withTextAndPosition("rubies", RubySpan.POSITION_UNKNOWN);
+
+ Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
+ assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
+ assertThat(thirdCue).hasNoRubySpanBetween(0, thirdCue.length());
+
+ Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000);
+ assertThat(fourthCue.toString()).isEqualTo("Cue with text.");
+ assertThat(fourthCue).hasNoRubySpanBetween(0, fourthCue.length());
+
+ Spanned fifthCue = getOnlyCueTextAtTimeUs(subtitle, 50_000_000);
+ assertThat(fifthCue.toString()).isEqualTo("Cue with annotated text.");
+ assertThat(fifthCue).hasNoRubySpanBetween(0, fifthCue.length());
+ }
+
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {
Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs);
assertThat(cue.text).isInstanceOf(Spanned.class);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java
index 8785229..f9d12ae 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java
@@ -35,12 +35,12 @@
public final class TtmlRenderUtilTest {
@Test
- public void testResolveStyleNoStyleAtAll() {
+ public void resolveStyleNoStyleAtAll() {
assertThat(resolveStyle(null, null, null)).isNull();
}
@Test
- public void testResolveStyleSingleReferentialStyle() {
+ public void resolveStyleSingleReferentialStyle() {
Map<String, TtmlStyle> globalStyles = getGlobalStyles();
String[] styleIds = {"s0"};
@@ -49,7 +49,7 @@
}
@Test
- public void testResolveStyleMultipleReferentialStyles() {
+ public void resolveStyleMultipleReferentialStyles() {
Map<String, TtmlStyle> globalStyles = getGlobalStyles();
String[] styleIds = {"s0", "s1"};
@@ -67,7 +67,7 @@
}
@Test
- public void testResolveMergeSingleReferentialStyleIntoInlineStyle() {
+ public void resolveMergeSingleReferentialStyleIntoInlineStyle() {
Map<String, TtmlStyle> globalStyles = getGlobalStyles();
String[] styleIds = {"s0"};
TtmlStyle style = new TtmlStyle();
@@ -83,7 +83,7 @@
}
@Test
- public void testResolveMergeMultipleReferentialStylesIntoInlineStyle() {
+ public void resolveMergeMultipleReferentialStylesIntoInlineStyle() {
Map<String, TtmlStyle> globalStyles = getGlobalStyles();
String[] styleIds = {"s0", "s1"};
TtmlStyle style = new TtmlStyle();
@@ -99,7 +99,7 @@
}
@Test
- public void testResolveStyleOnlyInlineStyle() {
+ public void resolveStyleOnlyInlineStyle() {
TtmlStyle inlineStyle = new TtmlStyle();
assertThat(TtmlRenderUtil.resolveStyle(inlineStyle, null, null)).isSameInstanceAs(inlineStyle);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
index 24b5ca6..4f75c50 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.text.ttml;
import static android.graphics.Color.BLACK;
-import static android.graphics.Color.WHITE;
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD;
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD_ITALIC;
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_ITALIC;
@@ -26,8 +25,11 @@
import static com.google.common.truth.Truth.assertWithMessage;
import android.graphics.Color;
+import android.text.Layout;
+import androidx.annotation.ColorInt;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import org.junit.Before;
+import com.google.android.exoplayer2.text.Cue;
+import com.google.android.exoplayer2.text.span.RubySpan;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,58 +37,93 @@
@RunWith(AndroidJUnit4.class)
public final class TtmlStyleTest {
- private static final String FONT_FAMILY = "serif";
private static final String ID = "id";
- public static final int FOREGROUND_COLOR = Color.WHITE;
- public static final int BACKGROUND_COLOR = Color.BLACK;
- private TtmlStyle style;
+ private static final String FONT_FAMILY = "serif";
+ @ColorInt private static final int FONT_COLOR = Color.WHITE;
+ private static final float FONT_SIZE = 12.5f;
+ @TtmlStyle.FontSizeUnit private static final int FONT_SIZE_UNIT = TtmlStyle.FONT_SIZE_UNIT_EM;
+ @ColorInt private static final int BACKGROUND_COLOR = Color.BLACK;
+ private static final int RUBY_TYPE = TtmlStyle.RUBY_TYPE_TEXT;
+ private static final int RUBY_POSITION = RubySpan.POSITION_UNDER;
+ private static final Layout.Alignment TEXT_ALIGN = Layout.Alignment.ALIGN_CENTER;
+ private static final boolean TEXT_COMBINE = true;
+ @Cue.VerticalType private static final int VERTICAL_TYPE = Cue.VERTICAL_TYPE_RL;
- @Before
- public void setUp() throws Exception {
- style = new TtmlStyle();
- }
+ private final TtmlStyle populatedStyle =
+ new TtmlStyle()
+ .setId(ID)
+ .setItalic(true)
+ .setBold(true)
+ .setBackgroundColor(BACKGROUND_COLOR)
+ .setFontColor(FONT_COLOR)
+ .setLinethrough(true)
+ .setUnderline(true)
+ .setFontFamily(FONT_FAMILY)
+ .setFontSize(FONT_SIZE)
+ .setFontSizeUnit(FONT_SIZE_UNIT)
+ .setRubyType(RUBY_TYPE)
+ .setRubyPosition(RUBY_POSITION)
+ .setTextAlign(TEXT_ALIGN)
+ .setTextCombine(TEXT_COMBINE)
+ .setVerticalType(VERTICAL_TYPE);
@Test
- public void testInheritStyle() {
- style.inherit(createAncestorStyle());
+ public void inheritStyle() {
+ TtmlStyle style = new TtmlStyle();
+ style.inherit(populatedStyle);
+
assertWithMessage("id must not be inherited").that(style.getId()).isNull();
assertThat(style.isUnderline()).isTrue();
assertThat(style.isLinethrough()).isTrue();
assertThat(style.getStyle()).isEqualTo(STYLE_BOLD_ITALIC);
assertThat(style.getFontFamily()).isEqualTo(FONT_FAMILY);
- assertThat(style.getFontColor()).isEqualTo(WHITE);
- assertWithMessage("do not inherit backgroundColor").that(style.hasBackgroundColor()).isFalse();
+ assertThat(style.getFontColor()).isEqualTo(FONT_COLOR);
+ assertThat(style.getFontSize()).isEqualTo(FONT_SIZE);
+ assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
+ assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
+ assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
+ assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
+ assertWithMessage("rubyType should not be inherited")
+ .that(style.getRubyType())
+ .isEqualTo(UNSPECIFIED);
+ assertWithMessage("backgroundColor should not be inherited")
+ .that(style.hasBackgroundColor())
+ .isFalse();
+ assertWithMessage("verticalType should not be inherited")
+ .that(style.getVerticalType())
+ .isEqualTo(Cue.TYPE_UNSET);
}
@Test
- public void testChainStyle() {
- style.chain(createAncestorStyle());
+ public void chainStyle() {
+ TtmlStyle style = new TtmlStyle();
+
+ style.chain(populatedStyle);
+
assertWithMessage("id must not be inherited").that(style.getId()).isNull();
assertThat(style.isUnderline()).isTrue();
assertThat(style.isLinethrough()).isTrue();
assertThat(style.getStyle()).isEqualTo(STYLE_BOLD_ITALIC);
assertThat(style.getFontFamily()).isEqualTo(FONT_FAMILY);
- assertThat(style.getFontColor()).isEqualTo(FOREGROUND_COLOR);
- // do inherit backgroundColor when chaining
- assertWithMessage("do not inherit backgroundColor when chaining")
- .that(style.getBackgroundColor()).isEqualTo(BACKGROUND_COLOR);
- }
-
- private static TtmlStyle createAncestorStyle() {
- TtmlStyle ancestor = new TtmlStyle();
- ancestor.setId(ID);
- ancestor.setItalic(true);
- ancestor.setBold(true);
- ancestor.setBackgroundColor(BACKGROUND_COLOR);
- ancestor.setFontColor(FOREGROUND_COLOR);
- ancestor.setLinethrough(true);
- ancestor.setUnderline(true);
- ancestor.setFontFamily(FONT_FAMILY);
- return ancestor;
+ assertThat(style.getFontColor()).isEqualTo(FONT_COLOR);
+ assertThat(style.getFontSize()).isEqualTo(FONT_SIZE);
+ assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
+ assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
+ assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
+ assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
+ assertWithMessage("backgroundColor should be chained")
+ .that(style.getBackgroundColor())
+ .isEqualTo(BACKGROUND_COLOR);
+ assertWithMessage("rubyType should be chained").that(style.getRubyType()).isEqualTo(RUBY_TYPE);
+ assertWithMessage("verticalType should be chained")
+ .that(style.getVerticalType())
+ .isEqualTo(VERTICAL_TYPE);
}
@Test
- public void testStyle() {
+ public void style() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.getStyle()).isEqualTo(UNSPECIFIED);
style.setItalic(true);
assertThat(style.getStyle()).isEqualTo(STYLE_ITALIC);
@@ -99,7 +136,9 @@
}
@Test
- public void testLinethrough() {
+ public void linethrough() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.isLinethrough()).isFalse();
style.setLinethrough(true);
assertThat(style.isLinethrough()).isTrue();
@@ -108,7 +147,9 @@
}
@Test
- public void testUnderline() {
+ public void underline() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.isUnderline()).isFalse();
style.setUnderline(true);
assertThat(style.isUnderline()).isTrue();
@@ -117,7 +158,9 @@
}
@Test
- public void testFontFamily() {
+ public void fontFamily() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.getFontFamily()).isNull();
style.setFontFamily(FONT_FAMILY);
assertThat(style.getFontFamily()).isEqualTo(FONT_FAMILY);
@@ -126,23 +169,47 @@
}
@Test
- public void testColor() {
+ public void fontColor() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.hasFontColor()).isFalse();
style.setFontColor(Color.BLACK);
- assertThat(style.getFontColor()).isEqualTo(BLACK);
assertThat(style.hasFontColor()).isTrue();
+ assertThat(style.getFontColor()).isEqualTo(BLACK);
}
@Test
- public void testBackgroundColor() {
+ public void fontSize() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getFontSize()).isEqualTo(0);
+ style.setFontSize(10.5f);
+ assertThat(style.getFontSize()).isEqualTo(10.5f);
+ }
+
+ @Test
+ public void fontSizeUnit() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getFontSizeUnit()).isEqualTo(UNSPECIFIED);
+ style.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_EM);
+ assertThat(style.getFontSizeUnit()).isEqualTo(TtmlStyle.FONT_SIZE_UNIT_EM);
+ }
+
+ @Test
+ public void backgroundColor() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.hasBackgroundColor()).isFalse();
style.setBackgroundColor(Color.BLACK);
- assertThat(style.getBackgroundColor()).isEqualTo(BLACK);
assertThat(style.hasBackgroundColor()).isTrue();
+ assertThat(style.getBackgroundColor()).isEqualTo(BLACK);
}
@Test
- public void testId() {
+ public void id() {
+ TtmlStyle style = new TtmlStyle();
+
assertThat(style.getId()).isNull();
style.setId(ID);
assertThat(style.getId()).isEqualTo(ID);
@@ -150,4 +217,41 @@
assertThat(style.getId()).isNull();
}
+ @Test
+ public void rubyType() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getRubyType()).isEqualTo(UNSPECIFIED);
+ style.setRubyType(TtmlStyle.RUBY_TYPE_BASE);
+ assertThat(style.getRubyType()).isEqualTo(TtmlStyle.RUBY_TYPE_BASE);
+ }
+
+ @Test
+ public void rubyPosition() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getRubyPosition()).isEqualTo(RubySpan.POSITION_UNKNOWN);
+ style.setRubyPosition(RubySpan.POSITION_OVER);
+ assertThat(style.getRubyPosition()).isEqualTo(RubySpan.POSITION_OVER);
+ }
+
+ @Test
+ public void textAlign() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getTextAlign()).isNull();
+ style.setTextAlign(Layout.Alignment.ALIGN_OPPOSITE);
+ assertThat(style.getTextAlign()).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
+ style.setTextAlign(null);
+ assertThat(style.getTextAlign()).isNull();
+ }
+
+ @Test
+ public void textCombine() {
+ TtmlStyle style = new TtmlStyle();
+
+ assertThat(style.getTextCombine()).isFalse();
+ style.setTextCombine(true);
+ assertThat(style.getTextCombine()).isTrue();
+ }
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java
index 16b997e..1433265 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java
@@ -54,7 +54,7 @@
private static final String INITIALIZATION_ALL_DEFAULTS = "tx3g/initialization_all_defaults";
@Test
- public void testDecodeNoSubtitle() throws IOException, SubtitleDecoderException {
+ public void decodeNoSubtitle() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NO_SUBTITLE);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
@@ -62,7 +62,7 @@
}
@Test
- public void testDecodeJustText() throws IOException, SubtitleDecoderException {
+ public void decodeJustText() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_JUST_TEXT);
@@ -74,7 +74,7 @@
}
@Test
- public void testDecodeWithStyl() throws IOException, SubtitleDecoderException {
+ public void decodeWithStyl() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL);
@@ -91,7 +91,7 @@
}
@Test
- public void testDecodeWithStylAllDefaults() throws IOException, SubtitleDecoderException {
+ public void decodeWithStylAllDefaults() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(
@@ -104,7 +104,7 @@
}
@Test
- public void testDecodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException {
+ public void decodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_UTF16_BE_NO_STYL);
@@ -116,7 +116,7 @@
}
@Test
- public void testDecodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException {
+ public void decodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), SAMPLE_UTF16_LE_NO_STYL);
@@ -128,7 +128,7 @@
}
@Test
- public void testDecodeWithMultipleStyl() throws IOException, SubtitleDecoderException {
+ public void decodeWithMultipleStyl() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(
@@ -148,7 +148,7 @@
}
@Test
- public void testDecodeWithOtherExtension() throws IOException, SubtitleDecoderException {
+ public void decodeWithOtherExtension() throws IOException, SubtitleDecoderException {
Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList());
byte[] bytes =
TestUtil.getByteArray(
@@ -165,7 +165,7 @@
}
@Test
- public void testInitializationDecodeWithStyl() throws IOException, SubtitleDecoderException {
+ public void initializationDecodeWithStyl() throws IOException, SubtitleDecoderException {
byte[] initBytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INITIALIZATION);
Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes));
@@ -188,7 +188,7 @@
}
@Test
- public void testInitializationDecodeWithTbox() throws IOException, SubtitleDecoderException {
+ public void initializationDecodeWithTbox() throws IOException, SubtitleDecoderException {
byte[] initBytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INITIALIZATION);
Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes));
@@ -209,7 +209,7 @@
}
@Test
- public void testInitializationAllDefaultsDecodeWithStyl()
+ public void initializationAllDefaultsDecodeWithStyl()
throws IOException, SubtitleDecoderException {
byte[] initBytes =
TestUtil.getByteArray(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java
index 72be083..7dc41ed 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java
@@ -38,7 +38,7 @@
}
@Test
- public void testSkipWhitespacesAndComments() {
+ public void skipWhitespacesAndComments() {
// Skip only whitespaces
String skipOnlyWhitespaces = " \t\r\n\f End of skip\n /* */";
assertSkipsToEndOfSkip("End of skip", skipOnlyWhitespaces);
@@ -61,7 +61,7 @@
}
@Test
- public void testGetInputLimit() {
+ public void getInputLimit() {
// \r After 3 lines.
String threeLinesThen3Cr = "One Line\nThen other\rAnd finally\r\r\r";
assertInputLimit("", threeLinesThen3Cr);
@@ -87,7 +87,7 @@
}
@Test
- public void testParseMethodSimpleInput() {
+ public void parseMethodSimpleInput() {
WebvttCssStyle expectedStyle = new WebvttCssStyle();
String styleBlock1 = " ::cue { color : black; background-color: PapayaWhip }";
expectedStyle.setFontColor(0xFF000000);
@@ -106,7 +106,7 @@
}
@Test
- public void testParseMethodMultipleRulesInBlockInput() {
+ public void parseMethodMultipleRulesInBlockInput() {
String styleBlock =
"::cue {\n background-color\n:#00fFFe} \n::cue {\n background-color\n:#00000000}\n";
WebvttCssStyle expectedStyle = new WebvttCssStyle();
@@ -117,7 +117,7 @@
}
@Test
- public void testMultiplePropertiesInBlock() {
+ public void multiplePropertiesInBlock() {
String styleBlock = "::cue(#id){text-decoration:underline; background-color:green;"
+ "color:red; font-family:Courier; font-weight:bold}";
WebvttCssStyle expectedStyle = new WebvttCssStyle();
@@ -132,7 +132,7 @@
}
@Test
- public void testRgbaColorExpression() {
+ public void rgbaColorExpression() {
String styleBlock = "::cue(#rgb){background-color: rgba(\n10/* Ugly color */,11\t, 12\n,.1);"
+ "color:rgb(1,1,\n1)}";
WebvttCssStyle expectedStyle = new WebvttCssStyle();
@@ -144,7 +144,7 @@
}
@Test
- public void testGetNextToken() {
+ public void getNextToken() {
String stringInput = " lorem:ipsum\n{dolor}#sit,amet;lorem:ipsum\r\t\f\ndolor(())\n";
ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(stringInput));
StringBuilder builder = new StringBuilder();
@@ -170,7 +170,7 @@
}
@Test
- public void testStyleScoreSystem() {
+ public void styleScoreSystem() {
WebvttCssStyle style = new WebvttCssStyle();
// Universal selector.
assertThat(style.getSpecificityScore("", "", new String[0], "")).isEqualTo(1);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java
index 5f91193..18a76c1 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java
@@ -88,7 +88,7 @@
// Positive tests.
@Test
- public void testSingleCueSample() throws SubtitleDecoderException {
+ public void singleCueSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length, false);
// Line feed must be trimmed by the decoder
@@ -97,7 +97,7 @@
}
@Test
- public void testTwoCuesSample() throws SubtitleDecoderException {
+ public void twoCuesSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length, false);
Cue firstExpectedCue = WebvttCueParser.newCueForText("Hello World");
@@ -106,7 +106,7 @@
}
@Test
- public void testNoCueSample() throws SubtitleDecoderException {
+ public void noCueSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length, false);
assertThat(result.getEventTimeCount()).isEqualTo(1);
@@ -117,7 +117,7 @@
// Negative tests.
@Test
- public void testSampleWithIncompleteHeader() {
+ public void sampleWithIncompleteHeader() {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
try {
decoder.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length, false);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java
index aa83fbc..f500029 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java
@@ -18,6 +18,7 @@
import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
+import android.graphics.Color;
import android.text.Spanned;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.text.span.RubySpan;
@@ -30,7 +31,7 @@
public final class WebvttCueParserTest {
@Test
- public void testParseStrictValidClassesAndTrailingTokens() throws Exception {
+ public void parseStrictValidClassesAndTrailingTokens() throws Exception {
Spanned text = parseCueText("<v.first.loud Esme>"
+ "This <u.style1.style2 some stuff>is</u> text with <b.foo><i.bar>html</i></b> tags");
@@ -41,7 +42,7 @@
}
@Test
- public void testParseStrictValidUnsupportedTagsStrippedOut() throws Exception {
+ public void parseStrictValidUnsupportedTagsStrippedOut() throws Exception {
Spanned text = parseCueText("<v.first.loud Esme>This <unsupported>is</unsupported> text with "
+ "<notsupp><invalid>html</invalid></notsupp> tags");
@@ -50,7 +51,7 @@
}
@Test
- public void testParseRubyTag() throws Exception {
+ public void parseRubyTag() throws Exception {
Spanned text =
parseCueText("Some <ruby>base text<rt>with ruby</rt></ruby> and undecorated text");
@@ -62,7 +63,30 @@
}
@Test
- public void testParseRubyTagWithNoTextTag() throws Exception {
+ public void parseSingleRubyTagWithMultipleRts() throws Exception {
+ Spanned text = parseCueText("<ruby>A<rt>1</rt>B<rt>2</rt>C<rt>3</rt></ruby>");
+
+ // The text between the <rt> tags is stripped from Cue.text and only present on the RubySpan.
+ assertThat(text.toString()).isEqualTo("ABC");
+ assertThat(text).hasRubySpanBetween(0, 1).withTextAndPosition("1", RubySpan.POSITION_OVER);
+ assertThat(text).hasRubySpanBetween(1, 2).withTextAndPosition("2", RubySpan.POSITION_OVER);
+ assertThat(text).hasRubySpanBetween(2, 3).withTextAndPosition("3", RubySpan.POSITION_OVER);
+ }
+
+ @Test
+ public void parseMultipleRubyTagsWithSingleRtEach() throws Exception {
+ Spanned text =
+ parseCueText("<ruby>A<rt>1</rt></ruby><ruby>B<rt>2</rt></ruby><ruby>C<rt>3</rt></ruby>");
+
+ // The text between the <rt> tags is stripped from Cue.text and only present on the RubySpan.
+ assertThat(text.toString()).isEqualTo("ABC");
+ assertThat(text).hasRubySpanBetween(0, 1).withTextAndPosition("1", RubySpan.POSITION_OVER);
+ assertThat(text).hasRubySpanBetween(1, 2).withTextAndPosition("2", RubySpan.POSITION_OVER);
+ assertThat(text).hasRubySpanBetween(2, 3).withTextAndPosition("3", RubySpan.POSITION_OVER);
+ }
+
+ @Test
+ public void parseRubyTagWithNoTextTag() throws Exception {
Spanned text = parseCueText("Some <ruby>base text with no ruby text</ruby>");
assertThat(text.toString()).isEqualTo("Some base text with no ruby text");
@@ -70,7 +94,7 @@
}
@Test
- public void testParseRubyTagWithEmptyTextTag() throws Exception {
+ public void parseRubyTagWithEmptyTextTag() throws Exception {
Spanned text = parseCueText("Some <ruby>base text with<rt></rt></ruby> empty ruby text");
assertThat(text.toString()).isEqualTo("Some base text with empty ruby text");
@@ -80,7 +104,48 @@
}
@Test
- public void testParseWellFormedUnclosedEndAtCueEnd() throws Exception {
+ public void parseDefaultTextColor() throws Exception {
+ Spanned text = parseCueText("In this sentence <c.red>this text</c> is red");
+
+ assertThat(text.toString()).isEqualTo("In this sentence this text is red");
+ assertThat(text)
+ .hasForegroundColorSpanBetween(
+ "In this sentence ".length(), "In this sentence this text".length())
+ .withColor(Color.RED);
+ }
+
+ @Test
+ public void parseUnsupportedDefaultTextColor() throws Exception {
+ Spanned text = parseCueText("In this sentence <c.papayawhip>this text</c> is not papaya");
+
+ assertThat(text.toString()).isEqualTo("In this sentence this text is not papaya");
+ assertThat(text).hasNoSpans();
+ }
+
+ @Test
+ public void parseDefaultBackgroundColor() throws Exception {
+ Spanned text = parseCueText("In this sentence <c.bg_cyan>this text</c> has a cyan background");
+
+ assertThat(text.toString()).isEqualTo("In this sentence this text has a cyan background");
+ assertThat(text)
+ .hasBackgroundColorSpanBetween(
+ "In this sentence ".length(), "In this sentence this text".length())
+ .withColor(Color.CYAN);
+ }
+
+ @Test
+ public void parseUnsupportedDefaultBackgroundColor() throws Exception {
+ Spanned text =
+ parseCueText(
+ "In this sentence <c.bg_papayawhip>this text</c> doesn't have a papaya background");
+
+ assertThat(text.toString())
+ .isEqualTo("In this sentence this text doesn't have a papaya background");
+ assertThat(text).hasNoSpans();
+ }
+
+ @Test
+ public void parseWellFormedUnclosedEndAtCueEnd() throws Exception {
Spanned text = parseCueText("An <u some trailing stuff>unclosed u tag with "
+ "<i>italic</i> inside");
@@ -93,7 +158,7 @@
}
@Test
- public void testParseWellFormedUnclosedEndAtParent() throws Exception {
+ public void parseWellFormedUnclosedEndAtParent() throws Exception {
Spanned text = parseCueText("An italic tag with unclosed <i><u>underline</i> inside");
assertThat(text.toString()).isEqualTo("An italic tag with unclosed underline inside");
@@ -108,7 +173,7 @@
}
@Test
- public void testParseMalformedNestedElements() throws Exception {
+ public void parseMalformedNestedElements() throws Exception {
Spanned text = parseCueText("<b><u>Overlapping u <i>and</u> i tags</i></b>");
String expectedText = "Overlapping u and i tags";
@@ -121,7 +186,7 @@
}
@Test
- public void testParseCloseNonExistingTag() throws Exception {
+ public void parseCloseNonExistingTag() throws Exception {
Spanned text = parseCueText("foo<b>bar</i>baz</b>buzz");
assertThat(text.toString()).isEqualTo("foobarbazbuzz");
@@ -130,49 +195,49 @@
}
@Test
- public void testParseEmptyTagName() throws Exception {
+ public void parseEmptyTagName() throws Exception {
Spanned text = parseCueText("An empty <>tag");
assertThat(text.toString()).isEqualTo("An empty tag");
}
@Test
- public void testParseEntities() throws Exception {
+ public void parseEntities() throws Exception {
Spanned text = parseCueText("& > < ");
assertThat(text.toString()).isEqualTo("& > < ");
}
@Test
- public void testParseEntitiesUnsupported() throws Exception {
+ public void parseEntitiesUnsupported() throws Exception {
Spanned text = parseCueText("&noway; &sure;");
assertThat(text.toString()).isEqualTo(" ");
}
@Test
- public void testParseEntitiesNotTerminated() throws Exception {
+ public void parseEntitiesNotTerminated() throws Exception {
Spanned text = parseCueText("& here comes text");
assertThat(text.toString()).isEqualTo("& here comes text");
}
@Test
- public void testParseEntitiesNotTerminatedUnsupported() throws Exception {
+ public void parseEntitiesNotTerminatedUnsupported() throws Exception {
Spanned text = parseCueText("&surenot here comes text");
assertThat(text.toString()).isEqualTo(" here comes text");
}
@Test
- public void testParseEntitiesNotTerminatedNoSpace() throws Exception {
+ public void parseEntitiesNotTerminatedNoSpace() throws Exception {
Spanned text = parseCueText("&surenot");
assertThat(text.toString()).isEqualTo("&surenot");
}
@Test
- public void testParseVoidTag() throws Exception {
+ public void parseVoidTag() throws Exception {
Spanned text = parseCueText("here comes<br/> text<br/>");
assertThat(text.toString()).isEqualTo("here comes text");
}
@Test
- public void testParseMultipleTagsOfSameKind() {
+ public void parseMultipleTagsOfSameKind() {
Spanned text = parseCueText("blah <b>blah</b> blah <b>foo</b>");
assertThat(text.toString()).isEqualTo("blah blah blah foo");
@@ -181,7 +246,7 @@
}
@Test
- public void testParseInvalidVoidSlash() {
+ public void parseInvalidVoidSlash() {
Spanned text = parseCueText("blah <b/.st1.st2 trailing stuff> blah");
assertThat(text.toString()).isEqualTo("blah blah");
@@ -189,7 +254,7 @@
}
@Test
- public void testParseMonkey() throws Exception {
+ public void parseMonkey() throws Exception {
Spanned text = parseCueText("< u>An unclosed u tag with <<<<< i>italic</u></u></u></u >"
+ "</i><u><u> inside");
assertThat(text.toString()).isEqualTo("An unclosed u tag with italic inside");
@@ -199,7 +264,7 @@
}
@Test
- public void testParseCornerCases() throws Exception {
+ public void parseCornerCases() throws Exception {
Spanned text = parseCueText(">");
assertThat(text.toString()).isEqualTo(">");
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java
index b778953..48f1340 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java
@@ -57,7 +57,7 @@
@Rule public final Expect expect = Expect.create();
@Test
- public void testDecodeEmpty() throws IOException {
+ public void decodeEmpty() throws IOException {
WebvttDecoder decoder = new WebvttDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), EMPTY_FILE);
try {
@@ -69,7 +69,7 @@
}
@Test
- public void testDecodeTypical() throws Exception {
+ public void decodeTypical() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -86,7 +86,7 @@
}
@Test
- public void testDecodeWithBom() throws Exception {
+ public void decodeWithBom() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_BOM);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -103,7 +103,7 @@
}
@Test
- public void testDecodeTypicalWithBadTimestamps() throws Exception {
+ public void decodeTypicalWithBadTimestamps() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_BAD_TIMESTAMPS);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -120,7 +120,7 @@
}
@Test
- public void testDecodeTypicalWithIds() throws Exception {
+ public void decodeTypicalWithIds() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_IDS_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -137,7 +137,7 @@
}
@Test
- public void testDecodeTypicalWithComments() throws Exception {
+ public void decodeTypicalWithComments() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_COMMENTS_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -154,7 +154,7 @@
}
@Test
- public void testDecodeWithTags() throws Exception {
+ public void decodeWithTags() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_TAGS_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(8);
@@ -181,7 +181,7 @@
}
@Test
- public void testDecodeWithPositioning() throws Exception {
+ public void decodeWithPositioning() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_POSITIONING_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(12);
@@ -249,7 +249,7 @@
}
@Test
- public void testDecodeWithVertical() throws Exception {
+ public void decodeWithVertical() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_VERTICAL_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(6);
@@ -274,7 +274,7 @@
}
@Test
- public void testDecodeWithBadCueHeader() throws Exception {
+ public void decodeWithBadCueHeader() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_BAD_CUE_HEADER_FILE);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
@@ -291,7 +291,7 @@
}
@Test
- public void testWebvttWithCssStyle() throws Exception {
+ public void webvttWithCssStyle() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_STYLES);
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 0);
@@ -323,7 +323,7 @@
}
@Test
- public void testWithComplexCssSelectors() throws Exception {
+ public void withComplexCssSelectors() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_COMPLEX_SELECTORS);
Spanned firstCueText = getUniqueSpanTextAt(subtitle, /* timeUs= */ 0);
assertThat(firstCueText).hasUnderlineSpanBetween(0, firstCueText.length());
@@ -362,7 +362,7 @@
}
@Test
- public void testWebvttWithCssTextCombineUpright() throws Exception {
+ public void webvttWithCssTextCombineUpright() throws Exception {
WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_TEXT_COMBINE_UPRIGHT);
Spanned firstCueText = getUniqueSpanTextAt(subtitle, 500_000);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java
index 61c6394..af44120 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java
@@ -75,7 +75,7 @@
/* endTimeUs= */ 3_000_000)));
@Test
- public void testEventCount() {
+ public void eventCount() {
assertThat(emptySubtitle.getEventTimeCount()).isEqualTo(0);
assertThat(simpleSubtitle.getEventTimeCount()).isEqualTo(4);
assertThat(overlappingSubtitle.getEventTimeCount()).isEqualTo(4);
@@ -83,17 +83,17 @@
}
@Test
- public void testSimpleSubtitleEventTimes() {
+ public void simpleSubtitleEventTimes() {
testSubtitleEventTimesHelper(simpleSubtitle);
}
@Test
- public void testSimpleSubtitleEventIndices() {
+ public void simpleSubtitleEventIndices() {
testSubtitleEventIndicesHelper(simpleSubtitle);
}
@Test
- public void testSimpleSubtitleText() {
+ public void simpleSubtitleText() {
// Test before first subtitle
assertSingleCueEmpty(simpleSubtitle.getCues(0));
assertSingleCueEmpty(simpleSubtitle.getCues(500_000));
@@ -121,17 +121,17 @@
}
@Test
- public void testOverlappingSubtitleEventTimes() {
+ public void overlappingSubtitleEventTimes() {
testSubtitleEventTimesHelper(overlappingSubtitle);
}
@Test
- public void testOverlappingSubtitleEventIndices() {
+ public void overlappingSubtitleEventIndices() {
testSubtitleEventIndicesHelper(overlappingSubtitle);
}
@Test
- public void testOverlappingSubtitleText() {
+ public void overlappingSubtitleText() {
// Test before first subtitle
assertSingleCueEmpty(overlappingSubtitle.getCues(0));
assertSingleCueEmpty(overlappingSubtitle.getCues(500_000));
@@ -162,17 +162,17 @@
}
@Test
- public void testNestedSubtitleEventTimes() {
+ public void nestedSubtitleEventTimes() {
testSubtitleEventTimesHelper(nestedSubtitle);
}
@Test
- public void testNestedSubtitleEventIndices() {
+ public void nestedSubtitleEventIndices() {
testSubtitleEventIndicesHelper(nestedSubtitle);
}
@Test
- public void testNestedSubtitleText() {
+ public void nestedSubtitleText() {
// Test before first subtitle
assertSingleCueEmpty(nestedSubtitle.getCues(0));
assertSingleCueEmpty(nestedSubtitle.getCues(500_000));
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
index af93504..b14e4b1 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
@@ -63,7 +63,7 @@
@Test
@SuppressWarnings("deprecation")
- public void testFactoryUsesInitiallyProvidedBandwidthMeter() {
+ public void factoryUsesInitiallyProvidedBandwidthMeter() {
BandwidthMeter initialBandwidthMeter = mock(BandwidthMeter.class);
BandwidthMeter injectedBandwidthMeter = mock(BandwidthMeter.class);
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
@@ -72,7 +72,7 @@
new AdaptiveTrackSelection.Factory(initialBandwidthMeter)
.createTrackSelections(
new Definition[] {
- new Definition(new TrackGroup(format1, format2), /* tracks= */ 0, 1)
+ new Definition(new TrackGroup(format1, format2), /* tracks=... */ 0, 1)
},
injectedBandwidthMeter);
trackSelections[0].updateSelectedTrack(
@@ -87,7 +87,7 @@
}
@Test
- public void testSelectInitialIndexUseMaxInitialBitrateIfNoBandwidthEstimate() {
+ public void selectInitialIndexUseMaxInitialBitrateIfNoBandwidthEstimate() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -101,7 +101,7 @@
}
@Test
- public void testSelectInitialIndexUseBandwidthEstimateIfAvailable() {
+ public void selectInitialIndexUseBandwidthEstimateIfAvailable() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -115,7 +115,7 @@
}
@Test
- public void testUpdateSelectedTrackDoNotSwitchUpIfNotBufferedEnough() {
+ public void updateSelectedTrackDoNotSwitchUpIfNotBufferedEnough() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -143,7 +143,7 @@
}
@Test
- public void testUpdateSelectedTrackSwitchUpIfBufferedEnough() {
+ public void updateSelectedTrackSwitchUpIfBufferedEnough() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -171,7 +171,7 @@
}
@Test
- public void testUpdateSelectedTrackDoNotSwitchDownIfBufferedEnough() {
+ public void updateSelectedTrackDoNotSwitchDownIfBufferedEnough() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -199,7 +199,7 @@
}
@Test
- public void testUpdateSelectedTrackSwitchDownIfNotBufferedEnough() {
+ public void updateSelectedTrackSwitchDownIfNotBufferedEnough() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -227,7 +227,7 @@
}
@Test
- public void testEvaluateQueueSizeReturnQueueSizeIfBandwidthIsNotImproved() {
+ public void evaluateQueueSizeReturnQueueSizeIfBandwidthIsNotImproved() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -252,7 +252,7 @@
}
@Test
- public void testEvaluateQueueSizeDoNotReevaluateUntilAfterMinTimeBetweenBufferReevaluation() {
+ public void evaluateQueueSizeDoNotReevaluateUntilAfterMinTimeBetweenBufferReevaluation() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -289,7 +289,7 @@
}
@Test
- public void testEvaluateQueueSizeRetainMoreThanMinimumDurationAfterDiscard() {
+ public void evaluateQueueSizeRetainMoreThanMinimumDurationAfterDiscard() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
@@ -406,17 +406,11 @@
}
private static Format videoFormat(int bitrate, int width, int height) {
- return Format.createVideoSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- /* bitrate= */ bitrate,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ width,
- /* height= */ height,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(bitrate)
+ .setWidth(width)
+ .setHeight(height)
+ .build();
}
-
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
index 62d3818..4304c9a 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
@@ -49,6 +49,7 @@
import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
@@ -79,8 +80,21 @@
private static final RendererCapabilities[] RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER =
new RendererCapabilities[] {VIDEO_CAPABILITIES, NO_SAMPLE_CAPABILITIES};
- private static final Format VIDEO_FORMAT = buildVideoFormat("video");
- private static final Format AUDIO_FORMAT = buildAudioFormat("audio");
+ private static final Format VIDEO_FORMAT =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setWidth(1024)
+ .setHeight(768)
+ .build();
+ private static final Format AUDIO_FORMAT =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setChannelCount(2)
+ .setSampleRate(44100)
+ .build();
+ private static final Format TEXT_FORMAT =
+ new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
+
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup(VIDEO_FORMAT);
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(AUDIO_FORMAT);
private static final TrackGroupArray TRACK_GROUPS =
@@ -119,7 +133,7 @@
/** Tests {@link Parameters} {@link android.os.Parcelable} implementation. */
@Test
- public void testParametersParcelable() {
+ public void parametersParcelable() {
SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides = new SparseArray<>();
Map<TrackGroupArray, SelectionOverride> videoOverrides = new HashMap<>();
videoOverrides.put(new TrackGroupArray(VIDEO_TRACK_GROUP), new SelectionOverride(0, 1));
@@ -175,7 +189,7 @@
/** Tests {@link SelectionOverride}'s {@link android.os.Parcelable} implementation. */
@Test
- public void testSelectionOverrideParcelable() {
+ public void selectionOverrideParcelable() {
int[] tracks = new int[] {2, 3};
SelectionOverride selectionOverrideToParcel =
new SelectionOverride(/* groupIndex= */ 1, tracks);
@@ -193,7 +207,7 @@
/** Tests that a null override clears a track selection. */
@Test
- public void testSelectTracksWithNullOverride() throws ExoPlaybackException {
+ public void selectTracksWithNullOverride() throws ExoPlaybackException {
trackSelector.setParameters(
trackSelector
.buildUponParameters()
@@ -207,7 +221,7 @@
/** Tests that a null override can be cleared. */
@Test
- public void testSelectTracksWithClearedNullOverride() throws ExoPlaybackException {
+ public void selectTracksWithClearedNullOverride() throws ExoPlaybackException {
trackSelector.setParameters(
trackSelector
.buildUponParameters()
@@ -222,7 +236,7 @@
/** Tests that an override is not applied for a different set of available track groups. */
@Test
- public void testSelectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException {
+ public void selectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException {
trackSelector.setParameters(
trackSelector
.buildUponParameters()
@@ -240,7 +254,7 @@
/** Tests disabling a renderer. */
@Test
- public void testSelectTracksWithDisabledRenderer() throws ExoPlaybackException {
+ public void selectTracksWithDisabledRenderer() throws ExoPlaybackException {
trackSelector.setParameters(defaultParameters.buildUpon().setRendererDisabled(1, true));
TrackSelectorResult result =
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE);
@@ -251,7 +265,7 @@
/** Tests that a disabled renderer can be enabled again. */
@Test
- public void testSelectTracksWithClearedDisabledRenderer() throws ExoPlaybackException {
+ public void selectTracksWithClearedDisabledRenderer() throws ExoPlaybackException {
trackSelector.setParameters(
trackSelector
.buildUponParameters()
@@ -266,7 +280,7 @@
/** Tests a no-sample renderer is enabled without a track selection by default. */
@Test
- public void testSelectTracksWithNoSampleRenderer() throws ExoPlaybackException {
+ public void selectTracksWithNoSampleRenderer() throws ExoPlaybackException {
TrackSelectorResult result =
trackSelector.selectTracks(
RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, periodId, TIMELINE);
@@ -277,7 +291,7 @@
/** Tests disabling a no-sample renderer. */
@Test
- public void testSelectTracksWithDisabledNoSampleRenderer() throws ExoPlaybackException {
+ public void selectTracksWithDisabledNoSampleRenderer() throws ExoPlaybackException {
trackSelector.setParameters(defaultParameters.buildUpon().setRendererDisabled(1, true));
TrackSelectorResult result =
trackSelector.selectTracks(
@@ -293,7 +307,7 @@
* {@link Parameters}.
*/
@Test
- public void testSetParameterWithDefaultParametersDoesNotNotifyInvalidationListener() {
+ public void setParameterWithDefaultParametersDoesNotNotifyInvalidationListener() {
trackSelector.setParameters(defaultParameters);
verify(invalidationListener, never()).onTrackSelectionsInvalidated();
}
@@ -303,7 +317,7 @@
* when it's set with non-default values of {@link Parameters}.
*/
@Test
- public void testSetParameterWithNonDefaultParameterNotifyInvalidationListener() {
+ public void setParameterWithNonDefaultParameterNotifyInvalidationListener() {
ParametersBuilder builder = defaultParameters.buildUpon().setPreferredAudioLanguage("eng");
trackSelector.setParameters(builder);
verify(invalidationListener).onTrackSelectionsInvalidated();
@@ -315,7 +329,7 @@
* of {@link Parameters}.
*/
@Test
- public void testSetParameterWithSameParametersDoesNotNotifyInvalidationListenerAgain() {
+ public void setParameterWithSameParametersDoesNotNotifyInvalidationListenerAgain() {
ParametersBuilder builder = defaultParameters.buildUpon().setPreferredAudioLanguage("eng");
trackSelector.setParameters(builder);
trackSelector.setParameters(builder);
@@ -323,17 +337,15 @@
}
/**
- * Tests that track selector will select audio track with {@link C#SELECTION_FLAG_DEFAULT}
- * given default values of {@link Parameters}.
+ * Tests that track selector will select audio track with {@link C#SELECTION_FLAG_DEFAULT} given
+ * default values of {@link Parameters}.
*/
@Test
- public void testSelectTracksSelectTrackWithSelectionFlag() throws Exception {
- Format audioFormat =
- buildAudioFormatWithLanguageAndFlags(
- "audio", /* language= */ null, /* selectionFlags= */ 0);
+ public void selectTracksSelectTrackWithSelectionFlag() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format audioFormat = formatBuilder.setSelectionFlags(0).build();
Format formatWithSelectionFlag =
- buildAudioFormatWithLanguageAndFlags(
- "audio", /* language= */ null, C.SELECTION_FLAG_DEFAULT);
+ formatBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT).build();
TrackGroupArray trackGroups = wrapFormats(audioFormat, formatWithSelectionFlag);
TrackSelectorResult result =
@@ -346,46 +358,12 @@
}
/** Tests that adaptive audio track selections respect the maximum audio bitrate. */
- public void testSelectAdaptiveAudioTrackGroupWithMaxBitrate() throws ExoPlaybackException {
- Format format128k =
- Format.createAudioSampleFormat(
- /* id= */ "128",
- /* sampleMimeType= */ MimeTypes.AUDIO_AAC,
- /* codecs= */ "mp4a.40.2",
- /* bitrate= */ 128 * 1024,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
- Format format192k =
- Format.createAudioSampleFormat(
- /* id= */ "192",
- /* sampleMimeType= */ MimeTypes.AUDIO_AAC,
- /* codecs= */ "mp4a.40.2",
- /* bitrate= */ 192 * 1024,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
- Format format256k =
- Format.createAudioSampleFormat(
- /* id= */ "256",
- /* sampleMimeType= */ MimeTypes.AUDIO_AAC,
- /* codecs= */ "mp4a.40.2",
- /* bitrate= */ 256 * 1024,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ @Test
+ public void selectAdaptiveAudioTrackGroupWithMaxBitrate() throws ExoPlaybackException {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format format128k = formatBuilder.setAverageBitrate(128 * 1024).build();
+ Format format192k = formatBuilder.setAverageBitrate(192 * 1024).build();
+ Format format256k = formatBuilder.setAverageBitrate(256 * 1024).build();
RendererCapabilities[] rendererCapabilities = {
ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES
};
@@ -394,7 +372,7 @@
TrackSelectorResult result =
trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
- assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1, 2);
+ assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 2, 0, 1);
trackSelector.setParameters(
trackSelector.buildUponParameters().setMaxAudioBitrate(256 * 1024 - 1));
@@ -408,11 +386,11 @@
trackSelector.setParameters(
trackSelector.buildUponParameters().setMaxAudioBitrate(192 * 1024 - 1));
result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
- assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 1);
+ assertFixedSelection(result.selections.get(0), trackGroups.get(0), 1);
trackSelector.setParameters(trackSelector.buildUponParameters().setMaxAudioBitrate(10));
result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
- assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 1);
+ assertFixedSelection(result.selections.get(0), trackGroups.get(0), 1);
}
/**
@@ -420,14 +398,10 @@
* given by {@link Parameters}.
*/
@Test
- public void testSelectTracksSelectPreferredAudioLanguage()
- throws Exception {
- Format frAudioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, "fra");
- Format enAudioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, "eng");
+ public void selectTracksSelectPreferredAudioLanguage() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format frAudioFormat = formatBuilder.setLanguage("fra").build();
+ Format enAudioFormat = formatBuilder.setLanguage("eng").build();
TrackGroupArray trackGroups = wrapFormats(frAudioFormat, enAudioFormat);
trackSelector.setParameters(defaultParameters.buildUpon().setPreferredAudioLanguage("eng"));
@@ -445,15 +419,12 @@
* language given by {@link Parameters} over track with {@link C#SELECTION_FLAG_DEFAULT}.
*/
@Test
- public void testSelectTracksSelectPreferredAudioLanguageOverSelectionFlag()
- throws Exception {
- Format frAudioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, "fra");
- Format enAudioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, "eng");
- TrackGroupArray trackGroups = wrapFormats(frAudioFormat, enAudioFormat);
+ public void selectTracksSelectPreferredAudioLanguageOverSelectionFlag() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format frDefaultFormat =
+ formatBuilder.setLanguage("fra").setSelectionFlags(C.SELECTION_FLAG_DEFAULT).build();
+ Format enNonDefaultFormat = formatBuilder.setLanguage("eng").setSelectionFlags(0).build();
+ TrackGroupArray trackGroups = wrapFormats(frDefaultFormat, enNonDefaultFormat);
trackSelector.setParameters(defaultParameters.buildUpon().setPreferredAudioLanguage("eng"));
TrackSelectorResult result =
@@ -462,17 +433,18 @@
trackGroups,
periodId,
TIMELINE);
- assertFixedSelection(result.selections.get(0), trackGroups, enAudioFormat);
+ assertFixedSelection(result.selections.get(0), trackGroups, enNonDefaultFormat);
}
/**
- * Tests that track selector will prefer tracks that are within renderer's capabilities over
- * track that exceed renderer's capabilities.
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over track
+ * that exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksPreferTrackWithinCapabilities() throws Exception {
- Format supportedFormat = buildAudioFormat("supportedFormat");
- Format exceededFormat = buildAudioFormat("exceededFormat");
+ public void selectTracksPreferTrackWithinCapabilities() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format supportedFormat = formatBuilder.setId("supportedFormat").build();
+ Format exceededFormat = formatBuilder.setId("exceededFormat").build();
TrackGroupArray trackGroups = wrapFormats(exceededFormat, supportedFormat);
Map<String, Integer> mappedCapabilities = new HashMap<>();
@@ -495,12 +467,9 @@
* there are no other choice, given the default {@link Parameters}.
*/
@Test
- public void testSelectTracksWithNoTrackWithinCapabilitiesSelectExceededCapabilityTrack()
+ public void selectTracksWithNoTrackWithinCapabilitiesSelectExceededCapabilityTrack()
throws Exception {
- Format audioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
- TrackGroupArray trackGroups = singleTrackGroup(audioFormat);
+ TrackGroupArray trackGroups = singleTrackGroup(AUDIO_FORMAT);
TrackSelectorResult result =
trackSelector.selectTracks(
@@ -508,21 +477,18 @@
trackGroups,
periodId,
TIMELINE);
- assertFixedSelection(result.selections.get(0), trackGroups, audioFormat);
+ assertFixedSelection(result.selections.get(0), trackGroups, AUDIO_FORMAT);
}
/**
- * Tests that track selector will return a null track selection for a renderer when
- * all tracks exceed that renderer's capabilities when {@link Parameters} does not allow
+ * Tests that track selector will return a null track selection for a renderer when all tracks
+ * exceed that renderer's capabilities when {@link Parameters} does not allow
* exceeding-capabilities tracks.
*/
@Test
- public void testSelectTracksWithNoTrackWithinCapabilitiesAndSetByParamsReturnNoSelection()
+ public void selectTracksWithNoTrackWithinCapabilitiesAndSetByParamsReturnNoSelection()
throws Exception {
- Format audioFormat =
- Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
- TrackGroupArray trackGroups = singleTrackGroup(audioFormat);
+ TrackGroupArray trackGroups = singleTrackGroup(AUDIO_FORMAT);
trackSelector.setParameters(
defaultParameters.buildUpon().setExceedRendererCapabilitiesIfNecessary(false));
@@ -540,24 +506,11 @@
* tracks that have {@link C#SELECTION_FLAG_DEFAULT} but exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlag()
- throws Exception {
+ public void selectTracksPreferTrackWithinCapabilitiesOverSelectionFlag() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format exceededWithSelectionFlagFormat =
- Format.createAudioSampleFormat(
- "exceededFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 2,
- 44100,
- null,
- null,
- C.SELECTION_FLAG_DEFAULT,
- null);
- Format supportedFormat =
- Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
- Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ formatBuilder.setId("exceededFormat").setSelectionFlags(C.SELECTION_FLAG_DEFAULT).build();
+ Format supportedFormat = formatBuilder.setId("supportedFormat").setSelectionFlags(0).build();
TrackGroupArray trackGroups = wrapFormats(exceededWithSelectionFlagFormat, supportedFormat);
Map<String, Integer> mappedCapabilities = new HashMap<>();
@@ -576,29 +529,15 @@
}
/**
- * Tests that track selector will prefer tracks that are within renderer's capabilities over
- * track that have language matching preferred audio given by {@link Parameters} but exceed
- * renderer's capabilities.
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over track
+ * that have language matching preferred audio given by {@link Parameters} but exceed renderer's
+ * capabilities.
*/
@Test
- public void testSelectTracksPreferTrackWithinCapabilitiesOverPreferredLanguage()
- throws Exception {
- Format exceededEnFormat =
- Format.createAudioSampleFormat(
- "exceededFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 2,
- 44100,
- null,
- null,
- 0,
- "eng");
- Format supportedFrFormat =
- Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
- Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fra");
+ public void selectTracksPreferTrackWithinCapabilitiesOverPreferredLanguage() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format exceededEnFormat = formatBuilder.setId("exceededFormat").setLanguage("eng").build();
+ Format supportedFrFormat = formatBuilder.setId("supportedFormat").setLanguage("fra").build();
TrackGroupArray trackGroups = wrapFormats(exceededEnFormat, supportedFrFormat);
Map<String, Integer> mappedCapabilities = new HashMap<>();
@@ -618,29 +557,22 @@
}
/**
- * Tests that track selector will prefer tracks that are within renderer's capabilities over
- * track that have both language matching preferred audio given by {@link Parameters} and
- * {@link C#SELECTION_FLAG_DEFAULT}, but exceed renderer's capabilities.
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over track
+ * that have both language matching preferred audio given by {@link Parameters} and {@link
+ * C#SELECTION_FLAG_DEFAULT}, but exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlagAndPreferredLanguage()
+ public void selectTracksPreferTrackWithinCapabilitiesOverSelectionFlagAndPreferredLanguage()
throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format exceededDefaultSelectionEnFormat =
- Format.createAudioSampleFormat(
- "exceededFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 2,
- 44100,
- null,
- null,
- C.SELECTION_FLAG_DEFAULT,
- "eng");
+ formatBuilder
+ .setId("exceededFormat")
+ .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
+ .setLanguage("eng")
+ .build();
Format supportedFrFormat =
- Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
- Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fra");
+ formatBuilder.setId("supportedFormat").setSelectionFlags(0).setLanguage("fra").build();
TrackGroupArray trackGroups = wrapFormats(exceededDefaultSelectionEnFormat, supportedFrFormat);
Map<String, Integer> mappedCapabilities = new HashMap<>();
@@ -664,24 +596,10 @@
* are the same, and tracks are within renderer's capabilities.
*/
@Test
- public void testSelectTracksWithinCapabilitiesSelectHigherNumChannel()
- throws Exception {
- Format higherChannelFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 6,
- 44100,
- null,
- null,
- 0,
- null);
- Format lowerChannelFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ public void selectTracksWithinCapabilitiesSelectHigherNumChannel() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format higherChannelFormat = formatBuilder.setChannelCount(6).build();
+ Format lowerChannelFormat = formatBuilder.setChannelCount(2).build();
TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat);
TrackSelectorResult result =
@@ -698,14 +616,10 @@
* are the same, and tracks are within renderer's capabilities.
*/
@Test
- public void testSelectTracksWithinCapabilitiesSelectHigherSampleRate()
- throws Exception {
- Format higherSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
- Format lowerSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 22050, null, null, 0, null);
+ public void selectTracksWithinCapabilitiesSelectHigherSampleRate() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format higherSampleRateFormat = formatBuilder.setSampleRate(44100).build();
+ Format lowerSampleRateFormat = formatBuilder.setSampleRate(22050).build();
TrackGroupArray trackGroups = wrapFormats(higherSampleRateFormat, lowerSampleRateFormat);
TrackSelectorResult result =
@@ -724,32 +638,9 @@
@Test
public void selectAudioTracks_withinCapabilities_andSameLanguage_selectsHigherBitrate()
throws Exception {
- Format lowerBitrateFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 15000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ "hi");
- Format higherBitrateFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 30000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ "hi");
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon().setLanguage("en");
+ Format lowerBitrateFormat = formatBuilder.setAverageBitrate(15000).build();
+ Format higherBitrateFormat = formatBuilder.setAverageBitrate(30000).build();
TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat);
TrackSelectorResult result =
@@ -769,32 +660,9 @@
@Test
public void selectAudioTracks_withinCapabilities_andDifferentLanguage_selectsFirstTrack()
throws Exception {
- Format firstLanguageFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 15000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ "hi");
- Format higherBitrateFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 30000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ "te");
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format firstLanguageFormat = formatBuilder.setAverageBitrate(15000).setLanguage("hi").build();
+ Format higherBitrateFormat = formatBuilder.setAverageBitrate(30000).setLanguage("te").build();
TrackGroupArray trackGroups = wrapFormats(firstLanguageFormat, higherBitrateFormat);
TrackSelectorResult result =
@@ -812,23 +680,12 @@
* capabilities.
*/
@Test
- public void testSelectTracksPreferHigherNumChannelBeforeSampleRate() throws Exception {
+ public void selectTracksPreferHigherNumChannelBeforeSampleRate() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format higherChannelLowerSampleRateFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 6,
- 22050,
- null,
- null,
- 0,
- null);
+ formatBuilder.setChannelCount(6).setSampleRate(22050).build();
Format lowerChannelHigherSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ formatBuilder.setChannelCount(2).setSampleRate(44100).build();
TrackGroupArray trackGroups =
wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat);
@@ -843,18 +700,15 @@
/**
* Tests that track selector will prefer audio tracks with higher sample rate over tracks with
- * higher bitrate when other factors are the same, and tracks are within renderer's
- * capabilities.
+ * higher bitrate when other factors are the same, and tracks are within renderer's capabilities.
*/
@Test
- public void testSelectTracksPreferHigherSampleRateBeforeBitrate()
- throws Exception {
+ public void selectTracksPreferHigherSampleRateBeforeBitrate() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format higherSampleRateLowerBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ formatBuilder.setAverageBitrate(15000).setSampleRate(44100).build();
Format lowerSampleRateHigherBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
- Format.NO_VALUE, 2, 22050, null, null, 0, null);
+ formatBuilder.setAverageBitrate(30000).setSampleRate(22050).build();
TrackGroupArray trackGroups =
wrapFormats(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat);
@@ -872,24 +726,10 @@
* are the same, and tracks exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksExceedingCapabilitiesSelectLowerNumChannel()
- throws Exception {
- Format higherChannelFormat =
- Format.createAudioSampleFormat(
- "audioFormat",
- MimeTypes.AUDIO_AAC,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 6,
- 44100,
- null,
- null,
- 0,
- null);
- Format lowerChannelFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ public void selectTracksExceedingCapabilitiesSelectLowerNumChannel() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format higherChannelFormat = formatBuilder.setChannelCount(6).build();
+ Format lowerChannelFormat = formatBuilder.setChannelCount(2).build();
TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat);
TrackSelectorResult result =
@@ -906,14 +746,10 @@
* are the same, and tracks exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksExceedingCapabilitiesSelectLowerSampleRate()
- throws Exception {
- Format lowerSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 22050, null, null, 0, null);
- Format higherSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ public void selectTracksExceedingCapabilitiesSelectLowerSampleRate() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format lowerSampleRateFormat = formatBuilder.setSampleRate(22050).build();
+ Format higherSampleRateFormat = formatBuilder.setSampleRate(44100).build();
TrackGroupArray trackGroups = wrapFormats(higherSampleRateFormat, lowerSampleRateFormat);
TrackSelectorResult result =
@@ -926,18 +762,14 @@
}
/**
- * Tests that track selector will select audio tracks with lower bit-rate when other factors
- * are the same, and tracks exceed renderer's capabilities.
+ * Tests that track selector will select audio tracks with lower bit-rate when other factors are
+ * the same, and tracks exceed renderer's capabilities.
*/
@Test
- public void testSelectTracksExceedingCapabilitiesSelectLowerBitrate()
- throws Exception {
- Format lowerBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
- Format higherBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ public void selectTracksExceedingCapabilitiesSelectLowerBitrate() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format lowerBitrateFormat = formatBuilder.setAverageBitrate(15000).build();
+ Format higherBitrateFormat = formatBuilder.setAverageBitrate(30000).build();
TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat);
TrackSelectorResult result =
@@ -955,14 +787,13 @@
* capabilities.
*/
@Test
- public void testSelectTracksExceedingCapabilitiesPreferLowerNumChannelBeforeSampleRate()
+ public void selectTracksExceedingCapabilitiesPreferLowerNumChannelBeforeSampleRate()
throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format lowerChannelHigherSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ formatBuilder.setChannelCount(2).setSampleRate(44100).build();
Format higherChannelLowerSampleRateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
- Format.NO_VALUE, 6, 22050, null, null, 0, null);
+ formatBuilder.setChannelCount(6).setSampleRate(22050).build();
TrackGroupArray trackGroups =
wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat);
@@ -977,18 +808,16 @@
/**
* Tests that track selector will prefer audio tracks with lower sample rate over tracks with
- * lower bitrate when other factors are the same, and tracks are within renderer's
- * capabilities.
+ * lower bitrate when other factors are the same, and tracks are within renderer's capabilities.
*/
@Test
- public void testSelectTracksExceedingCapabilitiesPreferLowerSampleRateBeforeBitrate()
+ public void selectTracksExceedingCapabilitiesPreferLowerSampleRateBeforeBitrate()
throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format higherSampleRateLowerBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
- Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ formatBuilder.setAverageBitrate(15000).setSampleRate(44100).build();
Format lowerSampleRateHigherBitrateFormat =
- Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
- Format.NO_VALUE, 2, 22050, null, null, 0, null);
+ formatBuilder.setAverageBitrate(30000).setSampleRate(22050).build();
TrackGroupArray trackGroups =
wrapFormats(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat);
@@ -1003,12 +832,13 @@
/** Tests text track selection flags. */
@Test
- public void testTextTrackSelectionFlags() throws ExoPlaybackException {
- Format forcedOnly = buildTextFormat("forcedOnly", "eng", C.SELECTION_FLAG_FORCED);
+ public void textTrackSelectionFlags() throws ExoPlaybackException {
+ Format.Builder formatBuilder = TEXT_FORMAT.buildUpon().setLanguage("eng");
+ Format forcedOnly = formatBuilder.setSelectionFlags(C.SELECTION_FLAG_FORCED).build();
Format forcedDefault =
- buildTextFormat("forcedDefault", "eng", C.SELECTION_FLAG_FORCED | C.SELECTION_FLAG_DEFAULT);
- Format defaultOnly = buildTextFormat("defaultOnly", "eng", C.SELECTION_FLAG_DEFAULT);
- Format noFlag = buildTextFormat("noFlag", "eng");
+ formatBuilder.setSelectionFlags(C.SELECTION_FLAG_FORCED | C.SELECTION_FLAG_DEFAULT).build();
+ Format defaultOnly = formatBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT).build();
+ Format noFlag = formatBuilder.setSelectionFlags(0).build();
RendererCapabilities[] textRendererCapabilities =
new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES};
@@ -1068,26 +898,15 @@
* audio language when no text language preferences match.
*/
@Test
- public void testSelectingForcedTextTrackMatchesAudioLanguage() throws ExoPlaybackException {
- Format forcedEnglish =
- buildTextFormat(/* id= */ "forcedEnglish", /* language= */ "eng", C.SELECTION_FLAG_FORCED);
- Format forcedGerman =
- buildTextFormat(/* id= */ "forcedGerman", /* language= */ "deu", C.SELECTION_FLAG_FORCED);
- Format forcedNoLanguage =
- buildTextFormat(
- /* id= */ "forcedNoLanguage",
- /* language= */ C.LANGUAGE_UNDETERMINED,
- C.SELECTION_FLAG_FORCED);
- Format audio = buildAudioFormat(/* id= */ "audio");
- Format germanAudio =
- buildAudioFormat(
- /* id= */ "germanAudio",
- MimeTypes.AUDIO_AAC,
- /* bitrate= */ Format.NO_VALUE,
- "deu",
- /* selectionFlags= */ 0,
- /* channelCount= */ Format.NO_VALUE,
- /* sampleRate= */ Format.NO_VALUE);
+ public void selectingForcedTextTrackMatchesAudioLanguage() throws ExoPlaybackException {
+ Format.Builder formatBuilder =
+ TEXT_FORMAT.buildUpon().setSelectionFlags(C.SELECTION_FLAG_FORCED);
+ Format forcedEnglish = formatBuilder.setLanguage("eng").build();
+ Format forcedGerman = formatBuilder.setLanguage("deu").build();
+ Format forcedNoLanguage = formatBuilder.setLanguage(C.LANGUAGE_UNDETERMINED).build();
+
+ Format noLanguageAudio = AUDIO_FORMAT.buildUpon().setLanguage(null).build();
+ Format germanAudio = AUDIO_FORMAT.buildUpon().setLanguage("deu").build();
RendererCapabilities[] rendererCapabilities =
new RendererCapabilities[] {
@@ -1097,14 +916,14 @@
// Neither the audio nor the forced text track define a language. We select them both under the
// assumption that they have matching language.
- TrackGroupArray trackGroups = wrapFormats(audio, forcedNoLanguage);
+ TrackGroupArray trackGroups = wrapFormats(noLanguageAudio, forcedNoLanguage);
TrackSelectorResult result =
trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
assertFixedSelection(result.selections.get(1), trackGroups, forcedNoLanguage);
// No forced text track should be selected because none of the forced text tracks' languages
// matches the selected audio language.
- trackGroups = wrapFormats(audio, forcedEnglish, forcedGerman);
+ trackGroups = wrapFormats(noLanguageAudio, forcedEnglish, forcedGerman);
result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE);
assertNoSelection(result.selections.get(1));
@@ -1121,15 +940,16 @@
/**
* Tests that the default track selector will select a text track with undetermined language if no
- * text track with the preferred language is available but
- * {@link Parameters#selectUndeterminedTextLanguage} is true.
+ * text track with the preferred language is available but {@link
+ * Parameters#selectUndeterminedTextLanguage} is true.
*/
@Test
- public void testSelectUndeterminedTextLanguageAsFallback() throws ExoPlaybackException{
- Format spanish = buildTextFormat("spanish", "spa");
- Format german = buildTextFormat("german", "de");
- Format undeterminedUnd = buildTextFormat("undeterminedUnd", "und");
- Format undeterminedNull = buildTextFormat("undeterminedNull", null);
+ public void selectUndeterminedTextLanguageAsFallback() throws ExoPlaybackException {
+ Format.Builder formatBuilder = TEXT_FORMAT.buildUpon();
+ Format spanish = formatBuilder.setLanguage("spa").build();
+ Format german = formatBuilder.setLanguage("de").build();
+ Format undeterminedUnd = formatBuilder.setLanguage(C.LANGUAGE_UNDETERMINED).build();
+ Format undeterminedNull = formatBuilder.setLanguage(null).build();
RendererCapabilities[] textRendererCapabilites =
new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES};
@@ -1169,9 +989,10 @@
/** Tests audio track selection when there are multiple audio renderers. */
@Test
- public void testSelectPreferredTextTrackMultipleRenderers() throws Exception {
- Format english = buildTextFormat("en", "en");
- Format german = buildTextFormat("de", "de");
+ public void selectPreferredTextTrackMultipleRenderers() throws Exception {
+ Format.Builder formatBuilder = TEXT_FORMAT.buildUpon();
+ Format english = formatBuilder.setId("en").setLanguage("en").build();
+ Format german = formatBuilder.setId("de").setLanguage("de").build();
// First renderer handles english.
Map<String, Integer> firstRendererMappedCapabilities = new HashMap<>();
@@ -1215,11 +1036,13 @@
* Parameters#forceLowestBitrate} is set.
*/
@Test
- public void testSelectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate()
+ public void selectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate()
throws Exception {
- Format unsupportedLowBitrateFormat = buildAudioFormatWithBitrate("unsupportedLowBitrate", 5000);
- Format lowerBitrateFormat = buildAudioFormatWithBitrate("lowBitrate", 15000);
- Format higherBitrateFormat = buildAudioFormatWithBitrate("highBitrate", 30000);
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format unsupportedLowBitrateFormat =
+ formatBuilder.setId("unsupported").setAverageBitrate(5000).build();
+ Format lowerBitrateFormat = formatBuilder.setId("lower").setAverageBitrate(15000).build();
+ Format higherBitrateFormat = formatBuilder.setId("higher").setAverageBitrate(30000).build();
TrackGroupArray trackGroups =
wrapFormats(unsupportedLowBitrateFormat, lowerBitrateFormat, higherBitrateFormat);
@@ -1245,11 +1068,12 @@
* Parameters#forceHighestSupportedBitrate} is set.
*/
@Test
- public void testSelectTracksWithinCapabilitiesAndForceHighestBitrateSelectHigherBitrate()
+ public void selectTracksWithinCapabilitiesAndForceHighestBitrateSelectHigherBitrate()
throws Exception {
- Format lowerBitrateFormat = buildAudioFormatWithBitrate("lowerBitrateFormat", 5000);
- Format higherBitrateFormat = buildAudioFormatWithBitrate("higherBitrateFormat", 15000);
- Format exceedsBitrateFormat = buildAudioFormatWithBitrate("exceedsBitrateFormat", 30000);
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format lowerBitrateFormat = formatBuilder.setId("5000").setAverageBitrate(5000).build();
+ Format higherBitrateFormat = formatBuilder.setId("15000").setAverageBitrate(15000).build();
+ Format exceedsBitrateFormat = formatBuilder.setId("30000").setAverageBitrate(30000).build();
TrackGroupArray trackGroups =
wrapFormats(lowerBitrateFormat, higherBitrateFormat, exceedsBitrateFormat);
@@ -1272,8 +1096,10 @@
}
@Test
- public void testSelectTracksWithMultipleAudioTracks() throws Exception {
- TrackGroupArray trackGroups = singleTrackGroup(buildAudioFormat("0"), buildAudioFormat("1"));
+ public void selectTracksWithMultipleAudioTracks() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ TrackGroupArray trackGroups =
+ singleTrackGroup(formatBuilder.setId("0").build(), formatBuilder.setId("1").build());
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@@ -1283,11 +1109,41 @@
}
@Test
- public void testSelectTracksWithMultipleAudioTracksWithMixedSampleRates() throws Exception {
- Format highSampleRateAudioFormat =
- buildAudioFormatWithSampleRate("44100", /* sampleRate= */ 44100);
- Format lowSampleRateAudioFormat =
- buildAudioFormatWithSampleRate("22050", /* sampleRate= */ 22050);
+ public void selectTracks_multipleAudioTracks_selectsAllTracksInBestConfigurationOnly()
+ throws Exception {
+ TrackGroupArray trackGroups =
+ singleTrackGroup(
+ buildAudioFormatWithConfiguration(
+ /* id= */ "0", /* channelCount= */ 6, MimeTypes.AUDIO_AAC, /* sampleRate= */ 44100),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "1", /* channelCount= */ 2, MimeTypes.AUDIO_AAC, /* sampleRate= */ 44100),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "2", /* channelCount= */ 6, MimeTypes.AUDIO_AC3, /* sampleRate= */ 44100),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "3", /* channelCount= */ 6, MimeTypes.AUDIO_AAC, /* sampleRate= */ 22050),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "4", /* channelCount= */ 6, MimeTypes.AUDIO_AAC, /* sampleRate= */ 22050),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "5", /* channelCount= */ 6, MimeTypes.AUDIO_AAC, /* sampleRate= */ 22050),
+ buildAudioFormatWithConfiguration(
+ /* id= */ "6",
+ /* channelCount= */ 6,
+ MimeTypes.AUDIO_AAC,
+ /* sampleRate= */ 44100));
+
+ TrackSelectorResult result =
+ trackSelector.selectTracks(
+ new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
+
+ assertThat(result.length).isEqualTo(1);
+ assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 6);
+ }
+
+ @Test
+ public void selectTracksWithMultipleAudioTracksWithMixedSampleRates() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format highSampleRateAudioFormat = formatBuilder.setSampleRate(44100).build();
+ Format lowSampleRateAudioFormat = formatBuilder.setSampleRate(22050).build();
// Should not adapt between mixed sample rates by default, so we expect a fixed selection
// containing the higher sample rate stream.
@@ -1318,9 +1174,10 @@
}
@Test
- public void testSelectTracksWithMultipleAudioTracksWithMixedMimeTypes() throws Exception {
- Format aacAudioFormat = buildAudioFormatWithMimeType("aac", MimeTypes.AUDIO_AAC);
- Format opusAudioFormat = buildAudioFormatWithMimeType("opus", MimeTypes.AUDIO_OPUS);
+ public void selectTracksWithMultipleAudioTracksWithMixedMimeTypes() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format aacAudioFormat = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_AAC).build();
+ Format opusAudioFormat = formatBuilder.setSampleMimeType(MimeTypes.AUDIO_OPUS).build();
// Should not adapt between mixed mime types by default, so we expect a fixed selection
// containing the first stream.
@@ -1350,11 +1207,10 @@
}
@Test
- public void testSelectTracksWithMultipleAudioTracksWithMixedChannelCounts() throws Exception {
- Format stereoAudioFormat =
- buildAudioFormatWithChannelCount("2-channels", /* channelCount= */ 2);
- Format surroundAudioFormat =
- buildAudioFormatWithChannelCount("5-channels", /* channelCount= */ 5);
+ public void selectTracksWithMultipleAudioTracksWithMixedChannelCounts() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format stereoAudioFormat = formatBuilder.setChannelCount(2).build();
+ Format surroundAudioFormat = formatBuilder.setChannelCount(5).build();
// Should not adapt between different channel counts, so we expect a fixed selection containing
// the track with more channels.
@@ -1414,17 +1270,21 @@
}
@Test
- public void testSelectTracksWithMultipleAudioTracksOverrideReturnsAdaptiveTrackSelection()
+ public void selectTracksWithMultipleAudioTracksOverrideReturnsAdaptiveTrackSelection()
throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
TrackGroupArray trackGroups =
- singleTrackGroup(buildAudioFormat("0"), buildAudioFormat("1"), buildAudioFormat("2"));
+ singleTrackGroup(
+ formatBuilder.setId("0").build(),
+ formatBuilder.setId("1").build(),
+ formatBuilder.setId("2").build());
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setSelectionOverride(
/* rendererIndex= */ 0,
trackGroups,
- new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2)));
+ new SelectionOverride(/* groupIndex= */ 0, /* tracks=... */ 1, 2)));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@@ -1435,9 +1295,10 @@
/** Tests audio track selection when there are multiple audio renderers. */
@Test
- public void testSelectPreferredAudioTrackMultipleRenderers() throws Exception {
- Format english = buildAudioFormatWithLanguage("en", "en");
- Format german = buildAudioFormatWithLanguage("de", "de");
+ public void selectPreferredAudioTrackMultipleRenderers() throws Exception {
+ Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
+ Format english = formatBuilder.setId("en").setLanguage("en").build();
+ Format german = formatBuilder.setId("de").setLanguage("de").build();
// First renderer handles english.
Map<String, Integer> firstRendererMappedCapabilities = new HashMap<>();
@@ -1477,8 +1338,10 @@
}
@Test
- public void testSelectTracksWithMultipleVideoTracks() throws Exception {
- TrackGroupArray trackGroups = singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1"));
+ public void selectTracksWithMultipleVideoTracks() throws Exception {
+ Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
+ TrackGroupArray trackGroups =
+ singleTrackGroup(formatBuilder.setId("0").build(), formatBuilder.setId("1").build());
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@@ -1488,13 +1351,14 @@
}
@Test
- public void testSelectTracksWithMultipleVideoTracksWithNonSeamlessAdaptiveness()
- throws Exception {
+ public void selectTracksWithMultipleVideoTracksWithNonSeamlessAdaptiveness() throws Exception {
FakeRendererCapabilities nonSeamlessVideoCapabilities =
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO, FORMAT_HANDLED | ADAPTIVE_NOT_SEAMLESS);
// Should do non-seamless adaptiveness by default, so expect an adaptive selection.
- TrackGroupArray trackGroups = singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1"));
+ Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
+ TrackGroupArray trackGroups =
+ singleTrackGroup(formatBuilder.setId("0").build(), formatBuilder.setId("1").build());
trackSelector.setParameters(
defaultParameters.buildUpon().setAllowVideoNonSeamlessAdaptiveness(true));
TrackSelectorResult result =
@@ -1520,9 +1384,10 @@
}
@Test
- public void testSelectTracksWithMultipleVideoTracksWithMixedMimeTypes() throws Exception {
- Format h264VideoFormat = buildVideoFormatWithMimeType("h264", MimeTypes.VIDEO_H264);
- Format h265VideoFormat = buildVideoFormatWithMimeType("h265", MimeTypes.VIDEO_H265);
+ public void selectTracksWithMultipleVideoTracksWithMixedMimeTypes() throws Exception {
+ Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
+ Format h264VideoFormat = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H264).build();
+ Format h265VideoFormat = formatBuilder.setSampleMimeType(MimeTypes.VIDEO_H265).build();
// Should not adapt between mixed mime types by default, so we expect a fixed selection
// containing the first stream.
@@ -1552,17 +1417,21 @@
}
@Test
- public void testSelectTracksWithMultipleVideoTracksOverrideReturnsAdaptiveTrackSelection()
+ public void selectTracksWithMultipleVideoTracksOverrideReturnsAdaptiveTrackSelection()
throws Exception {
+ Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
TrackGroupArray trackGroups =
- singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1"), buildVideoFormat("2"));
+ singleTrackGroup(
+ formatBuilder.setId("0").build(),
+ formatBuilder.setId("1").build(),
+ formatBuilder.setId("2").build());
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setSelectionOverride(
/* rendererIndex= */ 0,
trackGroups,
- new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2)));
+ new SelectionOverride(/* groupIndex= */ 0, /* tracks=... */ 1, 2)));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@@ -1631,132 +1500,14 @@
return new TrackGroupArray(trackGroups);
}
- private static Format buildVideoFormatWithMimeType(String id, String mimeType) {
- return Format.createVideoSampleFormat(
- id,
- mimeType,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- 1024,
- 768,
- Format.NO_VALUE,
- null,
- null);
- }
-
- private static Format buildVideoFormat(String id) {
- return buildVideoFormatWithMimeType(id, MimeTypes.VIDEO_H264);
- }
-
- private static Format buildAudioFormatWithLanguage(String id, String language) {
- return buildAudioFormatWithLanguageAndFlags(id, language, /* selectionFlags= */ 0);
- }
-
- private static Format buildAudioFormatWithLanguageAndFlags(
- String id, String language, int selectionFlags) {
- return buildAudioFormat(
- id,
- MimeTypes.AUDIO_AAC,
- /* bitrate= */ Format.NO_VALUE,
- language,
- selectionFlags,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100);
- }
-
- private static Format buildAudioFormatWithBitrate(String id, int bitrate) {
- return buildAudioFormat(
- id,
- MimeTypes.AUDIO_AAC,
- bitrate,
- /* language= */ null,
- /* selectionFlags= */ 0,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100);
- }
-
- private static Format buildAudioFormatWithSampleRate(String id, int sampleRate) {
- return buildAudioFormat(
- id,
- MimeTypes.AUDIO_AAC,
- /* bitrate= */ Format.NO_VALUE,
- /* language= */ null,
- /* selectionFlags= */ 0,
- /* channelCount= */ 2,
- sampleRate);
- }
-
- private static Format buildAudioFormatWithChannelCount(String id, int channelCount) {
- return buildAudioFormat(
- id,
- MimeTypes.AUDIO_AAC,
- /* bitrate= */ Format.NO_VALUE,
- /* language= */ null,
- /* selectionFlags= */ 0,
- channelCount,
- /* sampleRate= */ 44100);
- }
-
- private static Format buildAudioFormatWithMimeType(String id, String mimeType) {
- return buildAudioFormat(
- id,
- mimeType,
- /* bitrate= */ Format.NO_VALUE,
- /* language= */ null,
- /* selectionFlags= */ 0,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100);
- }
-
- private static Format buildAudioFormat(String id) {
- return buildAudioFormat(
- id,
- MimeTypes.AUDIO_AAC,
- /* bitrate= */ Format.NO_VALUE,
- /* language= */ null,
- /* selectionFlags= */ 0,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100);
- }
-
- private static Format buildAudioFormat(
- String id,
- String mimeType,
- int bitrate,
- String language,
- int selectionFlags,
- int channelCount,
- int sampleRate) {
- return Format.createAudioSampleFormat(
- id,
- mimeType,
- /* codecs= */ null,
- bitrate,
- /* maxInputSize= */ Format.NO_VALUE,
- channelCount,
- sampleRate,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- selectionFlags,
- language);
- }
-
- private static Format buildTextFormat(String id, String language) {
- return buildTextFormat(id, language, /* selectionFlags= */ 0);
- }
-
- private static Format buildTextFormat(String id, String language, int selectionFlags) {
- return Format.createTextContainerFormat(
- id,
- /* label= */ null,
- /* containerMimeType= */ null,
- /* sampleMimeType= */ MimeTypes.TEXT_VTT,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- selectionFlags,
- /* roleFlags= */ 0,
- language);
+ private static Format buildAudioFormatWithConfiguration(
+ String id, int channelCount, String mimeType, int sampleRate) {
+ return new Format.Builder()
+ .setId(id)
+ .setSampleMimeType(mimeType)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .build();
}
/**
@@ -1797,6 +1548,11 @@
}
@Override
+ public String getName() {
+ return "FakeRenderer(" + Util.getTrackTypeString(trackType) + ")";
+ }
+
+ @Override
public int getTrackType() {
return trackType;
}
@@ -1841,6 +1597,11 @@
}
@Override
+ public String getName() {
+ return "FakeRenderer(" + Util.getTrackTypeString(trackType) + ")";
+ }
+
+ @Override
public int getTrackType() {
return trackType;
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java
index ca6cae3..5d5508f 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java
@@ -32,6 +32,7 @@
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,37 +48,9 @@
private static final RendererCapabilities METADATA_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_METADATA);
- private static final TrackGroup VIDEO_TRACK_GROUP =
- new TrackGroup(
- Format.createVideoSampleFormat(
- "video",
- MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null));
- private static final TrackGroup AUDIO_TRACK_GROUP =
- new TrackGroup(
- Format.createAudioSampleFormat(
- "audio",
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null));
- private static final TrackGroup METADATA_TRACK_GROUP =
- new TrackGroup(
- Format.createSampleFormat(
- "metadata", MimeTypes.APPLICATION_ID3, /* subsampleOffsetUs= */ 0));
+ private static final TrackGroup VIDEO_TRACK_GROUP = buildTrackGroup(MimeTypes.VIDEO_H264);
+ private static final TrackGroup AUDIO_TRACK_GROUP = buildTrackGroup(MimeTypes.AUDIO_AAC);
+ private static final TrackGroup METADATA_TRACK_GROUP = buildTrackGroup(MimeTypes.APPLICATION_ID3);
private static final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1);
@@ -89,7 +62,7 @@
}
@Test
- public void selectTracks_audioAndVideo_sameOrderAsRenderers_mappedToCorectRenderer()
+ public void selectTracks_audioAndVideo_sameOrderAsRenderers_mappedToCorrectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
RendererCapabilities[] rendererCapabilities =
@@ -103,7 +76,7 @@
}
@Test
- public void selectTracks_audioAndVideo_reverseOrderToRenderers_mappedToCorectRenderer()
+ public void selectTracks_audioAndVideo_reverseOrderToRenderers_mappedToCorrectRenderer()
throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray trackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
@@ -152,6 +125,10 @@
trackSelector.assertMappedTrackGroups(2, METADATA_TRACK_GROUP);
}
+ private static TrackGroup buildTrackGroup(String sampleMimeType) {
+ return new TrackGroup(new Format.Builder().setSampleMimeType(sampleMimeType).build());
+ }
+
/**
* A {@link MappingTrackSelector} that stashes the {@link MappedTrackInfo} passed to {@link
* #selectTracks(MappedTrackInfo, int[][][], int[])}.
@@ -194,6 +171,11 @@
}
@Override
+ public String getName() {
+ return "FakeRenderer(" + Util.getTrackTypeString(trackType) + ")";
+ }
+
+ @Override
public int getTrackType() {
return trackType;
}
@@ -211,7 +193,5 @@
public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
return ADAPTIVE_SEAMLESS;
}
-
}
-
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java
index 7e25eac..1f4790b 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java
@@ -26,10 +26,10 @@
@RunWith(AndroidJUnit4.class)
public final class AssetDataSourceTest {
- private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
+ private static final String DATA_PATH = "mp3/1024_incrementing_bytes.mp3";
@Test
- public void testReadFileUri() throws Exception {
+ public void readFileUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(ApplicationProvider.getApplicationContext());
DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH));
TestUtil.assertDataSourceContent(
@@ -40,7 +40,7 @@
}
@Test
- public void testReadAssetUri() throws Exception {
+ public void readAssetUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(ApplicationProvider.getApplicationContext());
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH));
TestUtil.assertDataSourceContent(
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java
index ee64e56..27cf243 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import java.io.IOException;
@@ -32,17 +33,17 @@
private static final byte[] TEST_DATA_ODD = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
@Test
- public void testFullReadSingleBytes() {
+ public void fullReadSingleBytes() {
readTestData(TEST_DATA, 0, C.LENGTH_UNSET, 1, 0, 1, false);
}
@Test
- public void testFullReadAllBytes() {
+ public void fullReadAllBytes() {
readTestData(TEST_DATA, 0, C.LENGTH_UNSET, 100, 0, 100, false);
}
@Test
- public void testLimitReadSingleBytes() {
+ public void limitReadSingleBytes() {
// Limit set to the length of the data.
readTestData(TEST_DATA, 0, TEST_DATA.length, 1, 0, 1, false);
// And less.
@@ -50,7 +51,7 @@
}
@Test
- public void testFullReadTwoBytes() {
+ public void fullReadTwoBytes() {
// Try with the total data length an exact multiple of the size of each individual read.
readTestData(TEST_DATA, 0, C.LENGTH_UNSET, 2, 0, 2, false);
// And not.
@@ -58,7 +59,7 @@
}
@Test
- public void testLimitReadTwoBytes() {
+ public void limitReadTwoBytes() {
// Try with the limit an exact multiple of the size of each individual read.
readTestData(TEST_DATA, 0, 6, 2, 0, 2, false);
// And not.
@@ -66,7 +67,7 @@
}
@Test
- public void testReadFromValidOffsets() {
+ public void readFromValidOffsets() {
// Read from an offset without bound.
readTestData(TEST_DATA, 1, C.LENGTH_UNSET, 1, 0, 1, false);
// And with bound.
@@ -78,7 +79,7 @@
}
@Test
- public void testReadFromInvalidOffsets() {
+ public void readFromInvalidOffsets() {
// Read from first invalid offset and check failure without bound.
readTestData(TEST_DATA, TEST_DATA.length, C.LENGTH_UNSET, 1, 0, 1, true);
// And with bound.
@@ -86,7 +87,7 @@
}
@Test
- public void testReadWithInvalidLength() {
+ public void readWithInvalidLength() {
// Read more data than is available.
readTestData(TEST_DATA, 0, TEST_DATA.length + 1, 1, 0, 1, true);
// And with bound.
@@ -112,7 +113,7 @@
boolean opened = false;
try {
// Open the source.
- long length = dataSource.open(new DataSpec(null, dataOffset, dataLength, null));
+ long length = dataSource.open(new DataSpec(Uri.EMPTY, dataOffset, dataLength));
opened = true;
assertThat(expectFailOnOpen).isFalse();
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java
index 680674a..0acbd74 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java
@@ -44,7 +44,7 @@
}
@Test
- public void testBase64Data() throws IOException {
+ public void base64Data() throws IOException {
DataSpec dataSpec = buildDataSpec(DATA_SCHEME_URI);
assertDataSourceContent(
schemeDataDataSource,
@@ -55,7 +55,7 @@
}
@Test
- public void testAsciiData() throws IOException {
+ public void asciiData() throws IOException {
assertDataSourceContent(
schemeDataDataSource,
buildDataSpec("data:,A%20brief%20note"),
@@ -63,7 +63,7 @@
}
@Test
- public void testPartialReads() throws IOException {
+ public void partialReads() throws IOException {
byte[] buffer = new byte[18];
DataSpec dataSpec = buildDataSpec("data:,012345678901234567");
assertThat(schemeDataDataSource.open(dataSpec)).isEqualTo(18);
@@ -76,7 +76,7 @@
}
@Test
- public void testSequentialRangeRequests() throws IOException {
+ public void sequentialRangeRequests() throws IOException {
DataSpec dataSpec =
buildDataSpec(DATA_SCHEME_URI, /* position= */ 1, /* length= */ C.LENGTH_UNSET);
assertDataSourceContent(
@@ -97,7 +97,7 @@
}
@Test
- public void testInvalidStartPositionRequest() throws IOException {
+ public void invalidStartPositionRequest() throws IOException {
try {
// Try to open a range starting one byte beyond the resource's length.
schemeDataDataSource.open(
@@ -109,7 +109,7 @@
}
@Test
- public void testRangeExceedingResourceLengthRequest() throws IOException {
+ public void rangeExceedingResourceLengthRequest() throws IOException {
try {
// Try to open a range exceeding the resource's length.
schemeDataDataSource.open(
@@ -121,7 +121,7 @@
}
@Test
- public void testIncorrectScheme() {
+ public void incorrectScheme() {
try {
schemeDataDataSource.open(buildDataSpec("http://www.google.com"));
fail();
@@ -131,7 +131,7 @@
}
@Test
- public void testMalformedData() {
+ public void malformedData() {
try {
schemeDataDataSource.open(buildDataSpec("data:text/plain;base64,,This%20is%20Content"));
fail();
@@ -151,7 +151,7 @@
}
private static DataSpec buildDataSpec(String uriString, int position, int length) {
- return new DataSpec(Uri.parse(uriString), position, length, /* key= */ null);
+ return new DataSpec(Uri.parse(uriString), position, length);
}
/**
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java
index e982369..5bc3e4d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java
@@ -33,7 +33,7 @@
private static final byte[] TEST_DATA = TestUtil.buildTestData(16);
@Test
- public void testReadSingleBytes() throws IOException {
+ public void readSingleBytes() throws IOException {
DataSourceInputStream inputStream = buildTestInputStream();
// No bytes read yet.
assertThat(inputStream.bytesRead()).isEqualTo(0);
@@ -53,7 +53,7 @@
}
@Test
- public void testRead() throws IOException {
+ public void read() throws IOException {
DataSourceInputStream inputStream = buildTestInputStream();
// Read bytes.
byte[] readBytes = new byte[TEST_DATA.length];
@@ -76,7 +76,7 @@
}
@Test
- public void testSkip() throws IOException {
+ public void skip() throws IOException {
DataSourceInputStream inputStream = buildTestInputStream();
// Skip bytes.
long totalBytesSkipped = 0;
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java
index d724369..405fe6c 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -89,16 +88,13 @@
dataSpecRequestProperties.put("5", dataSpecParameter);
DataSpec dataSpec =
- new DataSpec(
- /* uri= */ Uri.parse("http://www.google.com"),
- /* httpMethod= */ 1,
- /* httpBody= */ new byte[] {0, 0, 0, 0},
- /* absoluteStreamPosition= */ 0,
- /* position= */ 0,
- /* length= */ 1,
- /* key= */ "key",
- /* flags= */ 0,
- dataSpecRequestProperties);
+ new DataSpec.Builder()
+ .setUri("http://www.google.com")
+ .setHttpBody(new byte[] {0, 0, 0, 0})
+ .setLength(1)
+ .setKey("key")
+ .setHttpRequestHeaders(dataSpecRequestProperties)
+ .build();
defaultHttpDataSource.open(dataSpec);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
index 27438fc..707ea92 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java
@@ -91,7 +91,7 @@
}
@Test
- public void testFragmentSize() throws Exception {
+ public void fragmentSize() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false);
assertReadDataContentLength(cacheDataSource, boundedDataSpec, false, false);
for (String key : cache.getKeys()) {
@@ -103,27 +103,27 @@
}
@Test
- public void testCacheAndReadUnboundedRequest() throws Exception {
+ public void cacheAndReadUnboundedRequest() throws Exception {
assertCacheAndRead(unboundedDataSpec, /* unknownLength= */ false);
}
@Test
- public void testCacheAndReadUnknownLength() throws Exception {
+ public void cacheAndReadUnknownLength() throws Exception {
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ true);
}
@Test
- public void testCacheAndReadUnboundedRequestUnknownLength() throws Exception {
+ public void cacheAndReadUnboundedRequestUnknownLength() throws Exception {
assertCacheAndRead(unboundedDataSpec, /* unknownLength= */ true);
}
@Test
- public void testCacheAndRead() throws Exception {
+ public void cacheAndRead() throws Exception {
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ false);
}
@Test
- public void testPropagatesHttpHeadersUpstream() throws Exception {
+ public void propagatesHttpHeadersUpstream() throws Exception {
CacheDataSource cacheDataSource =
createCacheDataSource(/* setReadException= */ false, /* unknownLength= */ false);
DataSpec dataSpec = buildDataSpec(/* position= */ 2, /* length= */ 5);
@@ -136,7 +136,7 @@
}
@Test
- public void testUnsatisfiableRange() throws Exception {
+ public void unsatisfiableRange() throws Exception {
// Bounded request but the content length is unknown. This forces all data to be cached but not
// the length.
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ true);
@@ -160,13 +160,13 @@
}
@Test
- public void testCacheAndReadUnboundedRequestWithCacheKeyFactoryWithNullDataSpecCacheKey()
+ public void cacheAndReadUnboundedRequestWithCacheKeyFactoryWithNullDataSpecCacheKey()
throws Exception {
assertCacheAndRead(unboundedDataSpec, /* unknownLength= */ false, cacheKeyFactory);
}
@Test
- public void testCacheAndReadUnknownLengthWithCacheKeyFactoryOverridingWithNullDataSpecCacheKey()
+ public void cacheAndReadUnknownLengthWithCacheKeyFactoryOverridingWithNullDataSpecCacheKey()
throws Exception {
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ true, cacheKeyFactory);
}
@@ -179,12 +179,12 @@
}
@Test
- public void testCacheAndReadWithCacheKeyFactoryWithNullDataSpecCacheKey() throws Exception {
+ public void cacheAndReadWithCacheKeyFactoryWithNullDataSpecCacheKey() throws Exception {
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ false, cacheKeyFactory);
}
@Test
- public void testUnsatisfiableRangeWithCacheKeyFactoryNullDataSpecCacheKey() throws Exception {
+ public void unsatisfiableRangeWithCacheKeyFactoryNullDataSpecCacheKey() throws Exception {
// Bounded request but the content length is unknown. This forces all data to be cached but not
// the length.
assertCacheAndRead(boundedDataSpec, /* unknownLength= */ true, cacheKeyFactory);
@@ -210,13 +210,13 @@
}
@Test
- public void testCacheAndReadUnboundedRequestWithCacheKeyFactoryOverridingDataSpecCacheKey()
+ public void cacheAndReadUnboundedRequestWithCacheKeyFactoryOverridingDataSpecCacheKey()
throws Exception {
assertCacheAndRead(unboundedDataSpecWithKey, false, cacheKeyFactory);
}
@Test
- public void testCacheAndReadUnknownLengthWithCacheKeyFactoryOverridingDataSpecCacheKey()
+ public void cacheAndReadUnknownLengthWithCacheKeyFactoryOverridingDataSpecCacheKey()
throws Exception {
assertCacheAndRead(boundedDataSpecWithKey, true, cacheKeyFactory);
}
@@ -229,13 +229,12 @@
}
@Test
- public void testCacheAndReadWithCacheKeyFactoryOverridingDataSpecCacheKey() throws Exception {
+ public void cacheAndReadWithCacheKeyFactoryOverridingDataSpecCacheKey() throws Exception {
assertCacheAndRead(boundedDataSpecWithKey, /* unknownLength= */ false, cacheKeyFactory);
}
@Test
- public void testUnsatisfiableRangeWithCacheKeyFactoryOverridingDataSpecCacheKey()
- throws Exception {
+ public void unsatisfiableRangeWithCacheKeyFactoryOverridingDataSpecCacheKey() throws Exception {
// Bounded request but the content length is unknown. This forces all data to be cached but not
// the length.
assertCacheAndRead(boundedDataSpecWithKey, /* unknownLength= */ true, cacheKeyFactory);
@@ -264,7 +263,7 @@
}
@Test
- public void testContentLengthEdgeCases() throws Exception {
+ public void contentLengthEdgeCases() throws Exception {
DataSpec dataSpec = buildDataSpec(TEST_DATA.length - 2, 2);
// Read partial at EOS but don't cross it so length is unknown.
@@ -286,16 +285,12 @@
// An unbounded request with offset for not cached content.
dataSpec =
- new DataSpec(
- Uri.parse("https://www.test.com/other"),
- TEST_DATA.length - 2,
- C.LENGTH_UNSET,
- /* key= */ null);
+ new DataSpec(Uri.parse("https://www.test.com/other"), TEST_DATA.length - 2, C.LENGTH_UNSET);
assertThat(cacheDataSource.open(dataSpec)).isEqualTo(C.LENGTH_UNSET);
}
@Test
- public void testUnknownLengthContentReadInOneConnectionAndLengthIsResolved() throws Exception {
+ public void unknownLengthContentReadInOneConnectionAndLengthIsResolved() throws Exception {
FakeDataSource upstream = new FakeDataSource();
upstream
.getDataSet()
@@ -314,7 +309,7 @@
}
@Test
- public void testIgnoreCacheForUnsetLengthRequests() throws Exception {
+ public void ignoreCacheForUnsetLengthRequests() throws Exception {
FakeDataSource upstream = new FakeDataSource();
upstream.getDataSet().setData(testDataUri, TEST_DATA);
CacheDataSource cacheDataSource =
@@ -329,14 +324,14 @@
}
@Test
- public void testReadOnlyCache() throws Exception {
+ public void readOnlyCache() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false, 0, null);
assertReadDataContentLength(cacheDataSource, boundedDataSpec, false, false);
assertCacheEmpty(cache);
}
@Test
- public void testSwitchToCacheSourceWithReadOnlyCacheDataSource() throws Exception {
+ public void switchToCacheSourceWithReadOnlyCacheDataSource() throws Exception {
// Create a fake data source with a 1 MB default data.
FakeDataSource upstream = new FakeDataSource();
FakeData fakeData = upstream.getDataSet().newDefaultData().appendReadData(1024 * 1024 - 1);
@@ -376,7 +371,7 @@
}
@Test
- public void testSwitchToCacheSourceWithNonBlockingCacheDataSource() throws Exception {
+ public void switchToCacheSourceWithNonBlockingCacheDataSource() throws Exception {
// Create a fake data source with a 1 MB default data.
FakeDataSource upstream = new FakeDataSource();
FakeData fakeData = upstream.getDataSet().newDefaultData().appendReadData(1024 * 1024 - 1);
@@ -425,7 +420,7 @@
}
@Test
- public void testDeleteCachedWhileReadingFromUpstreamWithReadOnlyCacheDataSourceDoesNotCrash()
+ public void deleteCachedWhileReadingFromUpstreamWithReadOnlyCacheDataSourceDoesNotCrash()
throws Exception {
// Create a fake data source with a 1 KB default data.
FakeDataSource upstream = new FakeDataSource();
@@ -461,7 +456,7 @@
}
@Test
- public void testDeleteCachedWhileReadingFromUpstreamWithBlockingCacheDataSourceDoesNotBlock()
+ public void deleteCachedWhileReadingFromUpstreamWithBlockingCacheDataSourceDoesNotBlock()
throws Exception {
// Create a fake data source with a 1 KB default data.
FakeDataSource upstream = new FakeDataSource();
@@ -546,7 +541,7 @@
private void assertReadData(
CacheDataSource cacheDataSource, DataSpec dataSpec, boolean unknownLength)
throws IOException {
- int position = (int) dataSpec.absoluteStreamPosition;
+ int position = (int) dataSpec.position;
int requestLength = (int) dataSpec.length;
int readLength = TEST_DATA.length - position;
if (requestLength != C.LENGTH_UNSET) {
@@ -624,12 +619,13 @@
}
private DataSpec buildDataSpec(long position, long length, @Nullable String key) {
- return new DataSpec(
- testDataUri,
- position,
- length,
- key,
- DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION,
- httpRequestHeaders);
+ return new DataSpec.Builder()
+ .setUri(testDataUri)
+ .setPosition(position)
+ .setLength(length)
+ .setKey(key)
+ .setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
+ .setHttpRequestHeaders(httpRequestHeaders)
+ .build();
}
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
index bb32f47..8702e88 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.copyOf;
import static java.util.Arrays.copyOfRange;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.content.Context;
import android.net.Uri;
@@ -39,8 +40,10 @@
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.annotation.LooperMode;
/** Additional tests for {@link CacheDataSource}. */
+@LooperMode(LEGACY)
@RunWith(AndroidJUnit4.class)
public final class CacheDataSourceTest2 {
@@ -48,25 +51,24 @@
private static final int EXO_CACHE_MAX_FILESIZE = 128;
private static final Uri URI = Uri.parse("http://test.com/content");
- private static final String KEY = "key";
private static final byte[] DATA = TestUtil.buildTestData(8 * EXO_CACHE_MAX_FILESIZE + 1);
// A DataSpec that covers the full file.
- private static final DataSpec FULL = new DataSpec(URI, 0, DATA.length, KEY);
+ private static final DataSpec FULL = new DataSpec(URI, 0, DATA.length);
private static final int OFFSET_ON_BOUNDARY = EXO_CACHE_MAX_FILESIZE;
// A DataSpec that starts at 0 and extends to a cache file boundary.
- private static final DataSpec END_ON_BOUNDARY = new DataSpec(URI, 0, OFFSET_ON_BOUNDARY, KEY);
+ private static final DataSpec END_ON_BOUNDARY = new DataSpec(URI, 0, OFFSET_ON_BOUNDARY);
// A DataSpec that starts on the same boundary and extends to the end of the file.
- private static final DataSpec START_ON_BOUNDARY = new DataSpec(URI, OFFSET_ON_BOUNDARY,
- DATA.length - OFFSET_ON_BOUNDARY, KEY);
+ private static final DataSpec START_ON_BOUNDARY =
+ new DataSpec(URI, OFFSET_ON_BOUNDARY, DATA.length - OFFSET_ON_BOUNDARY);
private static final int OFFSET_OFF_BOUNDARY = EXO_CACHE_MAX_FILESIZE * 2 + 1;
// A DataSpec that starts at 0 and extends to just past a cache file boundary.
- private static final DataSpec END_OFF_BOUNDARY = new DataSpec(URI, 0, OFFSET_OFF_BOUNDARY, KEY);
+ private static final DataSpec END_OFF_BOUNDARY = new DataSpec(URI, 0, OFFSET_OFF_BOUNDARY);
// A DataSpec that starts on the same boundary and extends to the end of the file.
- private static final DataSpec START_OFF_BOUNDARY = new DataSpec(URI, OFFSET_OFF_BOUNDARY,
- DATA.length - OFFSET_OFF_BOUNDARY, KEY);
+ private static final DataSpec START_OFF_BOUNDARY =
+ new DataSpec(URI, OFFSET_OFF_BOUNDARY, DATA.length - OFFSET_OFF_BOUNDARY);
@Test
public void testWithoutEncryption() throws IOException {
@@ -112,7 +114,7 @@
byte[] scratch = new byte[4096];
Random random = new Random(0);
source.open(dataSpec);
- int position = (int) dataSpec.absoluteStreamPosition;
+ int position = (int) dataSpec.position;
int bytesRead = 0;
while (bytesRead != C.RESULT_END_OF_INPUT) {
int maxBytesToRead = random.nextInt(scratch.length) + 1;
@@ -134,7 +136,6 @@
DataSpec[] openedDataSpecs = upstreamSource.getAndClearOpenedDataSpecs();
assertThat(openedDataSpecs).hasLength(1);
assertThat(openedDataSpecs[0].position).isEqualTo(start);
- assertThat(openedDataSpecs[0].absoluteStreamPosition).isEqualTo(start);
assertThat(openedDataSpecs[0].length).isEqualTo(end - start);
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java
index 9a449b2..65c9c4d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java
@@ -105,7 +105,7 @@
}
@Test
- public void testGenerateKey() {
+ public void generateKey() {
assertThat(CacheUtil.generateKey(Uri.EMPTY)).isNotNull();
Uri testUri = Uri.parse("test");
@@ -120,23 +120,23 @@
}
@Test
- public void testDefaultCacheKeyFactory_buildCacheKey() {
+ public void defaultCacheKeyFactory_buildCacheKey() {
Uri testUri = Uri.parse("test");
String key = "key";
// If DataSpec.key is present, returns it.
assertThat(
CacheUtil.DEFAULT_CACHE_KEY_FACTORY.buildCacheKey(
- new DataSpec(testUri, 0, LENGTH_UNSET, key)))
+ new DataSpec.Builder().setUri(testUri).setKey(key).build()))
.isEqualTo(key);
// If not generates a new one using DataSpec.uri.
assertThat(
CacheUtil.DEFAULT_CACHE_KEY_FACTORY.buildCacheKey(
- new DataSpec(testUri, 0, LENGTH_UNSET, null)))
+ new DataSpec(testUri, /* position= */ 0, /* length= */ LENGTH_UNSET)))
.isEqualTo(testUri.toString());
}
@Test
- public void testGetCachedNoData() {
+ public void getCachedNoData() {
Pair<Long, Long> contentLengthAndBytesCached =
CacheUtil.getCached(
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null);
@@ -146,7 +146,7 @@
}
@Test
- public void testGetCachedDataUnknownLength() {
+ public void getCachedDataUnknownLength() {
// Mock there is 100 bytes cached at the beginning
mockCache.spansAndGaps = new int[] {100};
Pair<Long, Long> contentLengthAndBytesCached =
@@ -158,7 +158,7 @@
}
@Test
- public void testGetCachedNoDataKnownLength() {
+ public void getCachedNoDataKnownLength() {
mockCache.contentLength = 1000;
Pair<Long, Long> contentLengthAndBytesCached =
CacheUtil.getCached(
@@ -169,7 +169,7 @@
}
@Test
- public void testGetCached() {
+ public void getCached() {
mockCache.contentLength = 1000;
mockCache.spansAndGaps = new int[] {100, 100, 200};
Pair<Long, Long> contentLengthAndBytesCached =
@@ -181,16 +181,12 @@
}
@Test
- public void testGetCachedFromNonZeroPosition() {
+ public void getCachedFromNonZeroPosition() {
mockCache.contentLength = 1000;
mockCache.spansAndGaps = new int[] {100, 100, 200};
Pair<Long, Long> contentLengthAndBytesCached =
CacheUtil.getCached(
- new DataSpec(
- Uri.parse("test"),
- /* absoluteStreamPosition= */ 100,
- /* length= */ C.LENGTH_UNSET,
- /* key= */ null),
+ new DataSpec(Uri.parse("test"), /* position= */ 100, /* length= */ C.LENGTH_UNSET),
mockCache,
/* cacheKeyFactory= */ null);
@@ -199,7 +195,7 @@
}
@Test
- public void testCache() throws Exception {
+ public void cache() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
@@ -217,12 +213,12 @@
}
@Test
- public void testCacheSetOffsetAndLength() throws Exception {
+ public void cacheSetOffsetAndLength() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
Uri testUri = Uri.parse("test_data");
- DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
+ DataSpec dataSpec = new DataSpec(testUri, /* position= */ 10, /* length= */ 20);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
@@ -243,7 +239,7 @@
}
@Test
- public void testCacheUnknownLength() throws Exception {
+ public void cacheUnknownLength() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data")
.setSimulateUnknownLength(true)
.appendReadData(TestUtil.buildTestData(100)).endData();
@@ -259,14 +255,14 @@
}
@Test
- public void testCacheUnknownLengthPartialCaching() throws Exception {
+ public void cacheUnknownLengthPartialCaching() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data")
.setSimulateUnknownLength(true)
.appendReadData(TestUtil.buildTestData(100)).endData();
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
Uri testUri = Uri.parse("test_data");
- DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
+ DataSpec dataSpec = new DataSpec(testUri, /* position= */ 10, /* length= */ 20);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
@@ -287,12 +283,12 @@
}
@Test
- public void testCacheLengthExceedsActualDataLength() throws Exception {
+ public void cacheLengthExceedsActualDataLength() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
Uri testUri = Uri.parse("test_data");
- DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null);
+ DataSpec dataSpec = new DataSpec(testUri, /* position= */ 0, /* length= */ 1000);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
@@ -302,12 +298,12 @@
}
@Test
- public void testCacheThrowEOFException() throws Exception {
+ public void cacheThrowEOFException() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
Uri testUri = Uri.parse("test_data");
- DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null);
+ DataSpec dataSpec = new DataSpec(testUri, /* position= */ 0, /* length= */ 1000);
try {
CacheUtil.cache(
@@ -328,7 +324,7 @@
}
@Test
- public void testCachePolling() throws Exception {
+ public void cachePolling() throws Exception {
final CachingCounters counters = new CachingCounters();
FakeDataSet fakeDataSet =
new FakeDataSet()
@@ -354,12 +350,15 @@
}
@Test
- public void testRemove() throws Exception {
+ public void remove() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
- Uri uri = Uri.parse("test_data");
- DataSpec dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION);
+ DataSpec dataSpec =
+ new DataSpec.Builder()
+ .setUri("test_data")
+ .setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
+ .build();
CacheUtil.cache(
dataSpec,
cache,
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java
index ecb406e..bbb372b 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java
@@ -89,7 +89,7 @@
}
@Test
- public void testAddGetRemove() throws Exception {
+ public void addGetRemove() throws Exception {
final String key1 = "key1";
final String key2 = "key2";
final String key3 = "key3";
@@ -144,12 +144,12 @@
}
@Test
- public void testLegacyStoreAndLoad() throws Exception {
+ public void legacyStoreAndLoad() throws Exception {
assertStoredAndLoadedEqual(newLegacyInstance(), newLegacyInstance());
}
@Test
- public void testLegacyLoadV1() throws Exception {
+ public void legacyLoadV1() throws Exception {
CachedContentIndex index = newLegacyInstance();
FileOutputStream fos =
@@ -170,7 +170,7 @@
}
@Test
- public void testLegacyLoadV2() throws Exception {
+ public void legacyLoadV2() throws Exception {
CachedContentIndex index = newLegacyInstance();
FileOutputStream fos =
@@ -192,7 +192,7 @@
}
@Test
- public void testAssignIdForKeyAndGetKeyForId() {
+ public void assignIdForKeyAndGetKeyForId() {
CachedContentIndex index = newInstance();
final String key1 = "key1";
final String key2 = "key2";
@@ -206,7 +206,7 @@
}
@Test
- public void testGetNewId() {
+ public void getNewId() {
SparseArray<String> idToKey = new SparseArray<>();
assertThat(CachedContentIndex.getNewId(idToKey)).isEqualTo(0);
idToKey.put(10, "");
@@ -218,7 +218,7 @@
}
@Test
- public void testLegacyEncryption() throws Exception {
+ public void legacyEncryption() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
byte[] key2 = Util.getUtf8Bytes("Foo12345Foo12345"); // 128 bit key
@@ -270,7 +270,7 @@
}
@Test
- public void testRemoveEmptyNotLockedCachedContent() {
+ public void removeEmptyNotLockedCachedContent() {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
@@ -280,7 +280,7 @@
}
@Test
- public void testCantRemoveNotEmptyCachedContent() throws Exception {
+ public void cantRemoveNotEmptyCachedContent() throws Exception {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
@@ -298,7 +298,7 @@
}
@Test
- public void testCantRemoveLockedCachedContent() {
+ public void cantRemoveLockedCachedContent() {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
cachedContent.setLocked(true);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java
index a1c4d2b..a866f20 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java
@@ -75,13 +75,13 @@
}
@Test
- public void testGetRegion_noSpansInCache() {
+ public void getRegion_noSpansInCache() {
assertThat(tracker.getRegionEndTimeMs(100)).isEqualTo(CachedRegionTracker.NOT_CACHED);
assertThat(tracker.getRegionEndTimeMs(150)).isEqualTo(CachedRegionTracker.NOT_CACHED);
}
@Test
- public void testGetRegion_fullyCached() throws Exception {
+ public void getRegion_fullyCached() throws Exception {
tracker.onSpanAdded(cache, newCacheSpan(100, 100));
assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(CachedRegionTracker.CACHED_TO_END);
@@ -89,7 +89,7 @@
}
@Test
- public void testGetRegion_partiallyCached() throws Exception {
+ public void getRegion_partiallyCached() throws Exception {
tracker.onSpanAdded(cache, newCacheSpan(100, 40));
assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(200);
@@ -97,7 +97,7 @@
}
@Test
- public void testGetRegion_multipleSpanAddsJoinedCorrectly() throws Exception {
+ public void getRegion_multipleSpanAddsJoinedCorrectly() throws Exception {
tracker.onSpanAdded(cache, newCacheSpan(100, 20));
tracker.onSpanAdded(cache, newCacheSpan(120, 20));
@@ -106,7 +106,7 @@
}
@Test
- public void testGetRegion_fullyCachedThenPartiallyRemoved() throws Exception {
+ public void getRegion_fullyCachedThenPartiallyRemoved() throws Exception {
// Start with the full stream in cache.
tracker.onSpanAdded(cache, newCacheSpan(100, 100));
@@ -120,7 +120,7 @@
}
@Test
- public void testGetRegion_subchunkEstimation() throws Exception {
+ public void getRegion_subchunkEstimation() throws Exception {
tracker.onSpanAdded(cache, newCacheSpan(100, 10));
assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(50);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java
index 9c9b707..5dfc4f4 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java
@@ -34,35 +34,35 @@
}
@Test
- public void testContainsReturnsFalseWhenEmpty() throws Exception {
+ public void containsReturnsFalseWhenEmpty() throws Exception {
assertThat(contentMetadata.contains("test metadata")).isFalse();
}
@Test
- public void testContainsReturnsTrueForInitialValue() throws Exception {
+ public void containsReturnsTrueForInitialValue() throws Exception {
contentMetadata = createContentMetadata("metadata name", "value");
assertThat(contentMetadata.contains("metadata name")).isTrue();
}
@Test
- public void testGetReturnsDefaultValueWhenValueIsNotAvailable() throws Exception {
+ public void getReturnsDefaultValueWhenValueIsNotAvailable() throws Exception {
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
- public void testGetReturnsInitialValue() throws Exception {
+ public void getReturnsInitialValue() throws Exception {
contentMetadata = createContentMetadata("metadata name", "value");
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
}
@Test
- public void testEmptyMutationDoesNotFail() throws Exception {
+ public void emptyMutationDoesNotFail() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
DefaultContentMetadata.EMPTY.copyWithMutationsApplied(mutations);
}
@Test
- public void testAddNewMetadata() throws Exception {
+ public void addNewMetadata() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "value");
contentMetadata = contentMetadata.copyWithMutationsApplied(mutations);
@@ -70,7 +70,7 @@
}
@Test
- public void testAddNewIntMetadata() throws Exception {
+ public void addNewIntMetadata() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", 5);
contentMetadata = contentMetadata.copyWithMutationsApplied(mutations);
@@ -78,7 +78,7 @@
}
@Test
- public void testAddNewByteArrayMetadata() throws Exception {
+ public void addNewByteArrayMetadata() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
byte[] value = {1, 2, 3};
mutations.set("metadata name", value);
@@ -87,14 +87,14 @@
}
@Test
- public void testNewMetadataNotWrittenBeforeCommitted() throws Exception {
+ public void newMetadataNotWrittenBeforeCommitted() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "value");
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
- public void testEditMetadata() throws Exception {
+ public void editMetadata() throws Exception {
contentMetadata = createContentMetadata("metadata name", "value");
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "edited value");
@@ -103,7 +103,7 @@
}
@Test
- public void testRemoveMetadata() throws Exception {
+ public void removeMetadata() throws Exception {
contentMetadata = createContentMetadata("metadata name", "value");
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.remove("metadata name");
@@ -112,7 +112,7 @@
}
@Test
- public void testAddAndRemoveMetadata() throws Exception {
+ public void addAndRemoveMetadata() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "value");
mutations.remove("metadata name");
@@ -121,7 +121,7 @@
}
@Test
- public void testRemoveAndAddMetadata() throws Exception {
+ public void removeAndAddMetadata() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.remove("metadata name");
mutations.set("metadata name", "value");
@@ -130,14 +130,14 @@
}
@Test
- public void testEqualsStringValues() throws Exception {
+ public void equalsStringValues() throws Exception {
DefaultContentMetadata metadata1 = createContentMetadata("metadata1", "value");
DefaultContentMetadata metadata2 = createContentMetadata("metadata1", "value");
assertThat(metadata1).isEqualTo(metadata2);
}
@Test
- public void testEquals() throws Exception {
+ public void equals() throws Exception {
DefaultContentMetadata metadata1 =
createContentMetadata(
"metadata1", "value", "metadata2", 12345, "metadata3", new byte[] {1, 2, 3});
@@ -149,7 +149,7 @@
}
@Test
- public void testNotEquals() throws Exception {
+ public void notEquals() throws Exception {
DefaultContentMetadata metadata1 = createContentMetadata("metadata1", new byte[] {1, 2, 3});
DefaultContentMetadata metadata2 = createContentMetadata("metadata1", new byte[] {3, 2, 1});
assertThat(metadata1).isNotEqualTo(metadata2);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java
index 482174e..4984e71 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java
@@ -32,7 +32,7 @@
}
@Test
- public void testContentBiggerThanMaxSizeDoesNotThrowException() throws Exception {
+ public void contentBiggerThanMaxSizeDoesNotThrowException() throws Exception {
int maxBytes = 100;
LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxBytes);
evictor.onCacheInitialized();
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java
index 0686ae3..fa56c31 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java
@@ -53,7 +53,7 @@
}
@Test
- public void testCacheFile() throws Exception {
+ public void cacheFile() throws Exception {
assertCacheSpan("key1", 0, 0);
assertCacheSpan("key2", 1, 2);
assertCacheSpan("<>:\"/\\|?*%", 1, 2);
@@ -72,7 +72,7 @@
}
@Test
- public void testUpgradeFileName() throws Exception {
+ public void upgradeFileName() throws Exception {
String key = "abc%def";
int id = index.assignIdForKey(key);
File v3file = createTestFile(cacheDir, id + ".0.1.v3.exo");
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java
index 4d9a936..14222f1 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java
@@ -63,7 +63,7 @@
}
@Test
- public void testCacheInitialization() {
+ public void cacheInitialization() {
SimpleCache cache = getSimpleCache();
// Cache initialization should have created a non-negative UID.
@@ -79,7 +79,7 @@
}
@Test
- public void testCacheInitializationError() throws IOException {
+ public void cacheInitializationError() throws IOException {
// Creating a file where the cache should be will cause an error during initialization.
assertThat(cacheDir.createNewFile()).isTrue();
@@ -90,7 +90,7 @@
}
@Test
- public void testCommittingOneFile() throws Exception {
+ public void committingOneFile() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
@@ -122,7 +122,7 @@
}
@Test
- public void testReadCacheWithoutReleasingWriteCacheSpan() throws Exception {
+ public void readCacheWithoutReleasingWriteCacheSpan() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
@@ -133,7 +133,7 @@
}
@Test
- public void testSetGetContentMetadata() throws Exception {
+ public void setGetContentMetadata() throws Exception {
SimpleCache simpleCache = getSimpleCache();
assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1)))
@@ -173,7 +173,7 @@
}
@Test
- public void testReloadCache() throws Exception {
+ public void reloadCache() throws Exception {
SimpleCache simpleCache = getSimpleCache();
// write data
@@ -191,7 +191,7 @@
}
@Test
- public void testReloadCacheWithoutRelease() throws Exception {
+ public void reloadCacheWithoutRelease() throws Exception {
SimpleCache simpleCache = getSimpleCache();
// Write data for KEY_1.
@@ -226,7 +226,7 @@
}
@Test
- public void testEncryptedIndex() throws Exception {
+ public void encryptedIndex() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
@@ -245,7 +245,7 @@
}
@Test
- public void testEncryptedIndexWrongKey() throws Exception {
+ public void encryptedIndexWrongKey() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
@@ -265,7 +265,7 @@
}
@Test
- public void testEncryptedIndexLostKey() throws Exception {
+ public void encryptedIndexLostKey() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
@@ -284,7 +284,7 @@
}
@Test
- public void testGetCachedLength() throws Exception {
+ public void getCachedLength() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, 0);
@@ -320,7 +320,7 @@
/* Tests https://github.com/google/ExoPlayer/issues/3260 case. */
@Test
- public void testExceptionDuringEvictionByLeastRecentlyUsedCacheEvictorNotHang() throws Exception {
+ public void exceptionDuringEvictionByLeastRecentlyUsedCacheEvictorNotHang() throws Exception {
CachedContentIndex contentIndex =
Mockito.spy(new CachedContentIndex(TestUtil.getInMemoryDatabaseProvider()));
SimpleCache simpleCache =
@@ -357,7 +357,7 @@
}
@Test
- public void testUsingReleasedSimpleCacheThrowsException() throws Exception {
+ public void usingReleasedSimpleCacheThrowsException() throws Exception {
SimpleCache simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor());
simpleCache.release();
@@ -370,7 +370,7 @@
}
@Test
- public void testMultipleSimpleCacheWithSameCacheDirThrowsException() throws Exception {
+ public void multipleSimpleCacheWithSameCacheDirThrowsException() throws Exception {
new SimpleCache(cacheDir, new NoOpCacheEvictor());
try {
@@ -382,7 +382,7 @@
}
@Test
- public void testMultipleSimpleCacheWithSameCacheDirDoesNotThrowsExceptionAfterRelease()
+ public void multipleSimpleCacheWithSameCacheDirDoesNotThrowsExceptionAfterRelease()
throws Exception {
SimpleCache simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor());
simpleCache.release();
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java
index fde2bf5..17e69db 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java
@@ -76,7 +76,7 @@
// Test a single encrypt and decrypt call.
@Test
- public void testSingle() {
+ public void single() {
byte[] reference = TestUtil.buildTestData(DATA_LENGTH);
byte[] data = reference.clone();
@@ -92,7 +92,7 @@
// Test several encrypt and decrypt calls, each aligned on a 16 byte block size.
@Test
- public void testAligned() {
+ public void aligned() {
byte[] reference = TestUtil.buildTestData(DATA_LENGTH);
byte[] data = reference.clone();
Random random = new Random(RANDOM_SEED);
@@ -125,7 +125,7 @@
// Test several encrypt and decrypt calls, not aligned on block boundary.
@Test
- public void testUnAligned() {
+ public void unAligned() {
byte[] reference = TestUtil.buildTestData(DATA_LENGTH);
byte[] data = reference.clone();
Random random = new Random(RANDOM_SEED);
@@ -157,7 +157,7 @@
// Test decryption starting from the middle of an encrypted block.
@Test
- public void testMidJoin() {
+ public void midJoin() {
byte[] reference = TestUtil.buildTestData(DATA_LENGTH);
byte[] data = reference.clone();
Random random = new Random(RANDOM_SEED);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java
index c0bf459..cab14c2 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java
@@ -50,14 +50,14 @@
}
@Test
- public void testDelete() throws Exception {
+ public void delete() throws Exception {
assertThat(file.createNewFile()).isTrue();
atomicFile.delete();
assertThat(file.exists()).isFalse();
}
@Test
- public void testWriteRead() throws Exception {
+ public void writeRead() throws Exception {
OutputStream output = atomicFile.startWrite();
output.write(5);
atomicFile.endWrite(output);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java
index 2a1c59e..c2f165d 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java
@@ -35,29 +35,29 @@
// Negative tests.
@Test(expected = IllegalArgumentException.class)
- public void testParseUnknownColor() {
+ public void parseUnknownColor() {
ColorParser.parseTtmlColor("colorOfAnElectron");
}
@Test(expected = IllegalArgumentException.class)
- public void testParseNull() {
+ public void parseNull() {
ColorParser.parseTtmlColor(null);
}
@Test(expected = IllegalArgumentException.class)
- public void testParseEmpty() {
+ public void parseEmpty() {
ColorParser.parseTtmlColor("");
}
@Test(expected = IllegalArgumentException.class)
- public void testRgbColorParsingRgbValuesNegative() {
+ public void rgbColorParsingRgbValuesNegative() {
ColorParser.parseTtmlColor("rgb(-4, 55, 209)");
}
// Positive tests.
@Test
- public void testHexCodeParsing() {
+ public void hexCodeParsing() {
assertThat(parseTtmlColor("#FFFFFF")).isEqualTo(WHITE);
assertThat(parseTtmlColor("#FFFFFFFF")).isEqualTo(WHITE);
assertThat(parseTtmlColor("#123456")).isEqualTo(parseColor("#FF123456"));
@@ -67,14 +67,14 @@
}
@Test
- public void testRgbColorParsing() {
+ public void rgbColorParsing() {
assertThat(parseTtmlColor("rgb(255,255,255)")).isEqualTo(WHITE);
// Spaces are ignored.
assertThat(parseTtmlColor(" rgb ( 255, 255, 255)")).isEqualTo(WHITE);
}
@Test
- public void testRgbColorParsingRgbValuesOutOfBounds() {
+ public void rgbColorParsingRgbValuesOutOfBounds() {
int outOfBounds = ColorParser.parseTtmlColor("rgb(999, 999, 999)");
int color = Color.rgb(999, 999, 999);
// Behave like the framework does.
@@ -82,7 +82,7 @@
}
@Test
- public void testRgbaColorParsing() {
+ public void rgbaColorParsing() {
assertThat(parseTtmlColor("rgba(255,255,255,255)")).isEqualTo(WHITE);
assertThat(parseTtmlColor("rgba(255,255,255,255)"))
.isEqualTo(argb(255, 255, 255, 255));
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcherTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcherTest.java
new file mode 100644
index 0000000..5e5a6be
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/MediaSourceEventDispatcherTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.util;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.drm.DrmSessionEventListener;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+import com.google.android.exoplayer2.source.MediaSourceEventListener;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link MediaSourceEventDispatcher}. */
+@RunWith(AndroidJUnit4.class)
+public class MediaSourceEventDispatcherTest {
+
+ private static final MediaSource.MediaPeriodId MEDIA_PERIOD_ID =
+ new MediaSource.MediaPeriodId("test uid");
+ private static final int WINDOW_INDEX = 200;
+ private static final int MEDIA_TIME_OFFSET_MS = 1_000;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock private MediaSourceEventListener mediaSourceEventListener;
+ @Mock private MediaAndDrmEventListener mediaAndDrmEventListener;
+
+ private MediaSourceEventDispatcher eventDispatcher;
+
+ @Before
+ public void setupEventDispatcher() {
+ eventDispatcher = new MediaSourceEventDispatcher();
+ eventDispatcher =
+ eventDispatcher.withParameters(WINDOW_INDEX, MEDIA_PERIOD_ID, MEDIA_TIME_OFFSET_MS);
+ }
+
+ @Test
+ public void listenerReceivesEventPopulatedWithMediaPeriodInfo() {
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+ }
+
+ @Test
+ public void sameListenerObjectRegisteredTwiceOnlyReceivesEventsOnce() {
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+ }
+
+ @Test
+ public void sameListenerInstanceCanBeRegisteredWithTwoTypes() {
+ eventDispatcher.addEventListener(
+ new Handler(Looper.getMainLooper()),
+ mediaAndDrmEventListener,
+ MediaSourceEventListener.class);
+ eventDispatcher.addEventListener(
+ new Handler(Looper.getMainLooper()),
+ mediaAndDrmEventListener,
+ DrmSessionEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(),
+ DrmSessionEventListener.class);
+
+ verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+ verify(mediaAndDrmEventListener).onDrmKeysLoaded();
+ }
+
+ // If a listener is added that implements multiple types, it should only receive events for the
+ // type specified at registration time.
+ @Test
+ public void listenerOnlyReceivesEventsForRegisteredType() {
+ eventDispatcher.addEventListener(
+ new Handler(Looper.getMainLooper()),
+ mediaAndDrmEventListener,
+ MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+ eventDispatcher.dispatch(
+ (listener, windowIndex, mediaPeriodId) -> listener.onDrmKeysLoaded(),
+ DrmSessionEventListener.class);
+
+ verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+ verify(mediaAndDrmEventListener, never()).onDrmKeysLoaded();
+ }
+
+ @Test
+ public void listenerDoesntReceiveEventsDispatchedToSubclass() {
+ SubclassListener subclassListener = mock(SubclassListener.class);
+ eventDispatcher.addEventListener(
+ new Handler(Looper.getMainLooper()), subclassListener, MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(SubclassListener::subclassMethod, SubclassListener.class);
+
+ // subclassListener can handle the call to subclassMethod, but it isn't called because
+ // it was registered 'as-a' MediaSourceEventListener, not SubclassListener.
+ verify(subclassListener, never()).subclassMethod(anyInt(), any());
+ }
+
+ @Test
+ public void listenerDoesntReceiveEventsDispatchedToSuperclass() {
+ SubclassListener subclassListener = mock(SubclassListener.class);
+ eventDispatcher.addEventListener(
+ new Handler(Looper.getMainLooper()), subclassListener, SubclassListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ // subclassListener 'is-a' a MediaSourceEventListener, but it isn't called because the event
+ // is dispatched specifically to listeners registered as MediaSourceEventListener.
+ verify(subclassListener, never()).onMediaPeriodCreated(anyInt(), any());
+ }
+
+ @Test
+ public void listenersAreCopiedToNewDispatcher() {
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+
+ MediaSource.MediaPeriodId newPeriodId = new MediaSource.MediaPeriodId("different uid");
+ MediaSourceEventDispatcher newEventDispatcher =
+ this.eventDispatcher.withParameters(
+ /* windowIndex= */ 250, newPeriodId, /* mediaTimeOffsetMs= */ 500);
+
+ newEventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaSourceEventListener).onMediaPeriodCreated(250, newPeriodId);
+ }
+
+ @Test
+ public void removingListenerStopsEventDispatch() {
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+ eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaSourceEventListener, never()).onMediaPeriodCreated(anyInt(), any());
+ }
+
+ @Test
+ public void removingListenerWithDifferentTypeToRegistrationDoesntRemove() {
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaAndDrmEventListener, MediaSourceEventListener.class);
+ eventDispatcher.removeEventListener(mediaAndDrmEventListener, DrmSessionEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaAndDrmEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+ }
+
+ @Test
+ public void listenersAreCountedBasedOnListenerAndType() {
+ // Add the listener twice and remove it once.
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+ eventDispatcher.addEventListener(
+ Util.createHandler(), mediaSourceEventListener, MediaSourceEventListener.class);
+ eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class);
+
+ eventDispatcher.dispatch(
+ MediaSourceEventListener::onMediaPeriodCreated, MediaSourceEventListener.class);
+
+ verify(mediaSourceEventListener).onMediaPeriodCreated(WINDOW_INDEX, MEDIA_PERIOD_ID);
+
+ // Remove it a second time and confirm the events stop being propagated.
+ eventDispatcher.removeEventListener(mediaSourceEventListener, MediaSourceEventListener.class);
+
+ verifyNoMoreInteractions(mediaSourceEventListener);
+ }
+
+ private interface MediaAndDrmEventListener
+ extends MediaSourceEventListener, DrmSessionEventListener {}
+
+ private interface SubclassListener extends MediaSourceEventListener {
+ void subclassMethod(int windowIndex, @Nullable MediaPeriodId mediaPeriodId);
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java
index 77f5996..6ab273b 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java
@@ -30,7 +30,7 @@
private static final byte[] TEST_DATA_2 = Util.getUtf8Bytes("2 test data");
@Test
- public void testReset() throws Exception {
+ public void reset() throws Exception {
ByteArrayOutputStream byteArrayOutputStream1 = new ByteArrayOutputStream(1000);
ReusableBufferedOutputStream outputStream = new ReusableBufferedOutputStream(
byteArrayOutputStream1, 1000);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java
index f21e15a..8f1949f 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/TimedValueQueueTest.java
@@ -34,7 +34,7 @@
}
@Test
- public void testAddAndPollValues() {
+ public void addAndPollValues() {
queue.add(0, "a");
queue.add(1, "b");
queue.add(2, "c");
@@ -44,7 +44,7 @@
}
@Test
- public void testBufferCapacityIncreasesAutomatically() {
+ public void bufferCapacityIncreasesAutomatically() {
queue = new TimedValueQueue<>(1);
for (int i = 0; i < 20; i++) {
queue.add(i, "" + i);
@@ -56,7 +56,7 @@
}
@Test
- public void testTimeDiscontinuityClearsValues() {
+ public void timeDiscontinuityClearsValues() {
queue.add(1, "b");
queue.add(2, "c");
queue.add(0, "a");
@@ -65,7 +65,7 @@
}
@Test
- public void testTimeDiscontinuityOnFullBufferClearsValues() {
+ public void timeDiscontinuityOnFullBufferClearsValues() {
queue = new TimedValueQueue<>(2);
queue.add(1, "b");
queue.add(3, "c");
@@ -75,7 +75,7 @@
}
@Test
- public void testPollReturnsClosestValue() {
+ public void pollReturnsClosestValue() {
queue.add(0, "a");
queue.add(3, "b");
assertThat(queue.poll(2)).isEqualTo("b");
@@ -83,7 +83,7 @@
}
@Test
- public void testPollRemovesPreviousValues() {
+ public void pollRemovesPreviousValues() {
queue.add(0, "a");
queue.add(1, "b");
queue.add(2, "c");
@@ -92,7 +92,7 @@
}
@Test
- public void testPollFloorReturnsClosestPreviousValue() {
+ public void pollFloorReturnsClosestPreviousValue() {
queue.add(0, "a");
queue.add(3, "b");
assertThat(queue.pollFloor(2)).isEqualTo("a");
@@ -102,7 +102,7 @@
}
@Test
- public void testPollFloorRemovesPreviousValues() {
+ public void pollFloorRemovesPreviousValues() {
queue.add(0, "a");
queue.add(1, "b");
queue.add(2, "c");
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java
index 95d8a6b..6d1c27c 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java
@@ -30,11 +30,11 @@
/**
* Tests normal usage of {@link UriUtil#resolve(String, String)}.
- * <p>
- * The test cases are taken from RFC-3986 5.4.1.
+ *
+ * <p>The test cases are taken from RFC-3986 5.4.1.
*/
@Test
- public void testResolveNormal() {
+ public void resolveNormal() {
String base = "http://a/b/c/d;p?q";
assertThat(resolve(base, "g:h")).isEqualTo("g:h");
@@ -63,11 +63,11 @@
/**
* Tests abnormal usage of {@link UriUtil#resolve(String, String)}.
- * <p>
- * The test cases are taken from RFC-3986 5.4.2.
+ *
+ * <p>The test cases are taken from RFC-3986 5.4.2.
*/
@Test
- public void testResolveAbnormal() {
+ public void resolveAbnormal() {
String base = "http://a/b/c/d;p?q";
assertThat(resolve(base, "../../../g")).isEqualTo("http://a/g");
@@ -95,11 +95,9 @@
assertThat(resolve(base, "http:g")).isEqualTo("http:g");
}
- /**
- * Tests additional abnormal usage of {@link UriUtil#resolve(String, String)}.
- */
+ /** Tests additional abnormal usage of {@link UriUtil#resolve(String, String)}. */
@Test
- public void testResolveAbnormalAdditional() {
+ public void resolveAbnormalAdditional() {
assertThat(resolve("http://a/b", "c:d/../e")).isEqualTo("c:e");
assertThat(resolve("a:b", "../c")).isEqualTo("a:c");
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java
new file mode 100644
index 0000000..f4aee42
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/DecoderVideoRendererTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.video;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.Surface;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.RendererConfiguration;
+import com.google.android.exoplayer2.decoder.DecoderException;
+import com.google.android.exoplayer2.decoder.SimpleDecoder;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
+import com.google.android.exoplayer2.testutil.FakeSampleStream;
+import com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem;
+import com.google.android.exoplayer2.util.MimeTypes;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.shadows.ShadowLooper;
+
+/** Unit test for {@link DecoderVideoRenderer}. */
+@LooperMode(LooperMode.Mode.PAUSED)
+@RunWith(AndroidJUnit4.class)
+public final class DecoderVideoRendererTest {
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private static final Format H264_FORMAT =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setWidth(1920)
+ .setHeight(1080)
+ .build();
+
+ private DecoderVideoRenderer renderer;
+ @Mock private VideoRendererEventListener eventListener;
+
+ @Before
+ public void setUp() {
+ renderer =
+ new DecoderVideoRenderer(
+ /* allowedJoiningTimeMs= */ 0,
+ new Handler(),
+ eventListener,
+ /* maxDroppedFramesToNotify= */ -1) {
+
+ private final Object pendingDecodeCallLock = new Object();
+
+ @GuardedBy("pendingDecodeCallLock")
+ private int pendingDecodeCalls;
+
+ @C.VideoOutputMode private int outputMode;
+
+ @Override
+ public String getName() {
+ return "TestVideoRenderer";
+ }
+
+ @Override
+ @Capabilities
+ public int supportsFormat(Format format) {
+ return RendererCapabilities.create(FORMAT_HANDLED);
+ }
+
+ @Override
+ protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) {
+ this.outputMode = outputMode;
+ }
+
+ @Override
+ protected void renderOutputBufferToSurface(
+ VideoDecoderOutputBuffer outputBuffer, Surface surface) {
+ // Do nothing.
+ }
+
+ @Override
+ protected void onQueueInputBuffer(VideoDecoderInputBuffer buffer) {
+ // SimpleDecoder.decode() is called on a background thread we have no control about from
+ // the test. Ensure the background calls are predictably serialized by waiting for them
+ // to finish:
+ // 1. Mark decode calls as "pending" here.
+ // 2. Send a message on the test thread to wait for all pending decode calls.
+ // 3. Decrement the pending counter in decode calls and wake up the waiting test.
+ // 4. The tests need to call ShadowLooper.idleMainThread() to wait for pending calls.
+ synchronized (pendingDecodeCallLock) {
+ pendingDecodeCalls++;
+ }
+ new Handler()
+ .post(
+ () -> {
+ synchronized (pendingDecodeCallLock) {
+ while (pendingDecodeCalls > 0) {
+ try {
+ pendingDecodeCallLock.wait();
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+ }
+ });
+ super.onQueueInputBuffer(buffer);
+ }
+
+ @Override
+ protected SimpleDecoder<
+ VideoDecoderInputBuffer,
+ ? extends VideoDecoderOutputBuffer,
+ ? extends DecoderException>
+ createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
+ return new SimpleDecoder<
+ VideoDecoderInputBuffer, VideoDecoderOutputBuffer, DecoderException>(
+ new VideoDecoderInputBuffer[10], new VideoDecoderOutputBuffer[10]) {
+ @Override
+ protected VideoDecoderInputBuffer createInputBuffer() {
+ return new VideoDecoderInputBuffer();
+ }
+
+ @Override
+ protected VideoDecoderOutputBuffer createOutputBuffer() {
+ return new VideoDecoderOutputBuffer(this::releaseOutputBuffer);
+ }
+
+ @Override
+ protected DecoderException createUnexpectedDecodeException(Throwable error) {
+ return new DecoderException("error", error);
+ }
+
+ @Nullable
+ @Override
+ protected DecoderException decode(
+ VideoDecoderInputBuffer inputBuffer,
+ VideoDecoderOutputBuffer outputBuffer,
+ boolean reset) {
+ outputBuffer.init(inputBuffer.timeUs, outputMode, /* supplementalData= */ null);
+ synchronized (pendingDecodeCallLock) {
+ pendingDecodeCalls--;
+ pendingDecodeCallLock.notify();
+ }
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "TestDecoder";
+ }
+ };
+ }
+ };
+ renderer.setOutputSurface(new Surface(new SurfaceTexture(/* texName= */ 0)));
+ }
+
+ @Test
+ public void enable_withMayRenderStartOfStream_rendersFirstFrameBeforeStart() throws Exception {
+ FakeSampleStream fakeSampleStream =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME));
+
+ renderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {H264_FORMAT},
+ fakeSampleStream,
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ true,
+ /* offsetUs */ 0);
+ for (int i = 0; i < 10; i++) {
+ renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
+ // Ensure pending messages are delivered.
+ ShadowLooper.idleMainLooper();
+ }
+
+ verify(eventListener).onRenderedFirstFrame(any());
+ }
+
+ @Test
+ public void enable_withoutMayRenderStartOfStream_doesNotRenderFirstFrameBeforeStart()
+ throws Exception {
+ FakeSampleStream fakeSampleStream =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME));
+
+ renderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {H264_FORMAT},
+ fakeSampleStream,
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ false,
+ /* offsetUs */ 0);
+ for (int i = 0; i < 10; i++) {
+ renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
+ // Ensure pending messages are delivered.
+ ShadowLooper.idleMainLooper();
+ }
+
+ verify(eventListener, never()).onRenderedFirstFrame(any());
+ }
+
+ @Test
+ public void enable_withoutMayRenderStartOfStream_rendersFirstFrameAfterStart() throws Exception {
+ FakeSampleStream fakeSampleStream =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME));
+
+ renderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {H264_FORMAT},
+ fakeSampleStream,
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ false,
+ /* offsetUs */ 0);
+ renderer.start();
+ for (int i = 0; i < 10; i++) {
+ renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
+ // Ensure pending messages are delivered.
+ ShadowLooper.idleMainLooper();
+ }
+
+ verify(eventListener).onRenderedFirstFrame(any());
+ }
+
+ // TODO: Fix rendering of first frame at stream transition.
+ @Ignore
+ @Test
+ public void replaceStream_whenStarted_rendersFirstFrameOfNewStream() throws Exception {
+ FakeSampleStream fakeSampleStream1 =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
+ FakeSampleStreamItem.END_OF_STREAM_ITEM);
+ FakeSampleStream fakeSampleStream2 =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
+ FakeSampleStreamItem.END_OF_STREAM_ITEM);
+ renderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {H264_FORMAT},
+ fakeSampleStream1,
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ true,
+ /* offsetUs */ 0);
+ renderer.start();
+
+ boolean replacedStream = false;
+ for (int i = 0; i <= 10; i++) {
+ renderer.render(/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
+ if (!replacedStream && renderer.hasReadStreamToEnd()) {
+ renderer.replaceStream(new Format[] {H264_FORMAT}, fakeSampleStream2, /* offsetUs= */ 100);
+ replacedStream = true;
+ }
+ // Ensure pending messages are delivered.
+ ShadowLooper.idleMainLooper();
+ }
+
+ verify(eventListener, times(2)).onRenderedFirstFrame(any());
+ }
+
+ // TODO: Fix rendering of first frame at stream transition.
+ @Ignore
+ @Test
+ public void replaceStream_whenNotStarted_doesNotRenderFirstFrameOfNewStream() throws Exception {
+ FakeSampleStream fakeSampleStream1 =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
+ FakeSampleStreamItem.END_OF_STREAM_ITEM);
+ FakeSampleStream fakeSampleStream2 =
+ new FakeSampleStream(
+ /* format= */ H264_FORMAT,
+ /* eventDispatcher= */ null,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 50,
+ new FakeSampleStreamItem(new byte[] {0}, C.BUFFER_FLAG_KEY_FRAME),
+ FakeSampleStreamItem.END_OF_STREAM_ITEM);
+ renderer.enable(
+ RendererConfiguration.DEFAULT,
+ new Format[] {H264_FORMAT},
+ fakeSampleStream1,
+ /* positionUs= */ 0,
+ /* joining= */ false,
+ /* mayRenderStartOfStream= */ true,
+ /* offsetUs */ 0);
+
+ boolean replacedStream = false;
+ for (int i = 0; i < 10; i++) {
+ renderer.render(/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
+ if (!replacedStream && renderer.hasReadStreamToEnd()) {
+ renderer.replaceStream(new Format[] {H264_FORMAT}, fakeSampleStream2, /* offsetUs= */ 100);
+ replacedStream = true;
+ }
+ // Ensure pending messages are delivered.
+ ShadowLooper.idleMainLooper();
+ }
+
+ verify(eventListener).onRenderedFirstFrame(any());
+
+ // Render to streamOffsetUs and verify the new first frame gets rendered.
+ renderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000);
+
+ verify(eventListener, times(2)).onRenderedFirstFrame(any());
+ }
+}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java
index 887c8da..227a827 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueueTest.java
@@ -37,19 +37,19 @@
}
@Test
- public void testGetRotationMatrixReturnsNull_whenEmpty() throws Exception {
+ public void getRotationMatrixReturnsNull_whenEmpty() throws Exception {
assertThat(frameRotationQueue.pollRotationMatrix(rotationMatrix, 0)).isFalse();
}
@Test
- public void testGetRotationMatrixReturnsNotNull_whenNotEmpty() throws Exception {
+ public void getRotationMatrixReturnsNotNull_whenNotEmpty() throws Exception {
frameRotationQueue.setRotation(0, new float[] {1, 2, 3});
assertThat(frameRotationQueue.pollRotationMatrix(rotationMatrix, 0)).isTrue();
assertThat(rotationMatrix).hasLength(16);
}
@Test
- public void testConvertsAngleAxisToRotationMatrix() throws Exception {
+ public void convertsAngleAxisToRotationMatrix() throws Exception {
doTestAngleAxisToRotationMatrix(/* angleRadian= */ 0, /* x= */ 1, /* y= */ 0, /* z= */ 0);
frameRotationQueue.reset();
doTestAngleAxisToRotationMatrix(/* angleRadian= */ 1, /* x= */ 1, /* y= */ 0, /* z= */ 0);
@@ -61,7 +61,7 @@
}
@Test
- public void testRecentering_justYaw() throws Exception {
+ public void recentering_justYaw() throws Exception {
float[] actualMatrix =
getRotationMatrixFromAngleAxis(
/* angleRadian= */ (float) Math.PI, /* x= */ 0, /* y= */ 1, /* z= */ 0);
@@ -71,7 +71,7 @@
}
@Test
- public void testRecentering_yawAndPitch() throws Exception {
+ public void recentering_yawAndPitch() throws Exception {
float[] matrix =
getRotationMatrixFromAngleAxis(
/* angleRadian= */ (float) Math.PI, /* x= */ 1, /* y= */ 1, /* z= */ 0);
@@ -80,7 +80,7 @@
}
@Test
- public void testRecentering_yawAndPitch2() throws Exception {
+ public void recentering_yawAndPitch2() throws Exception {
float[] matrix =
getRotationMatrixFromAngleAxis(
/* angleRadian= */ (float) Math.PI / 2, /* x= */ 1, /* y= */ 1, /* z= */ 0);
@@ -90,7 +90,7 @@
}
@Test
- public void testRecentering_yawAndPitchAndRoll() throws Exception {
+ public void recentering_yawAndPitchAndRoll() throws Exception {
float[] matrix =
getRotationMatrixFromAngleAxis(
/* angleRadian= */ (float) Math.PI * 2 / 3, /* x= */ 1, /* y= */ 1, /* z= */ 1);
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java
index 1dadfe9..b955981 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionDecoderTest.java
@@ -44,12 +44,12 @@
private static final float[] LAST_UV = {1.0f, 1.0f};
@Test
- public void testDecodeProj() {
+ public void decodeProj() {
testDecoding(PROJ_DATA);
}
@Test
- public void testDecodeMshp() {
+ public void decodeMshp() {
testDecoding(Arrays.copyOfRange(PROJ_DATA, MSHP_OFFSET, PROJ_DATA.length));
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java
index 8add0ea..1a889bb 100644
--- a/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/video/spherical/ProjectionTest.java
@@ -37,7 +37,7 @@
private static final float HORIZONTAL_FOV_DEGREES = 360;
@Test
- public void testSphericalMesh() throws Exception {
+ public void sphericalMesh() throws Exception {
// Only the first param is important in this test.
Projection projection =
Projection.createEquirectangular(
@@ -61,7 +61,7 @@
}
@Test
- public void testArgumentValidation() {
+ public void argumentValidation() {
checkIllegalArgumentException(0, 1, 1, 1, 1);
checkIllegalArgumentException(1, 0, 1, 1, 1);
checkIllegalArgumentException(1, 1, 0, 1, 1);
diff --git a/tree/library/dash/build.gradle b/tree/library/dash/build.gradle
index f51fc50..0ffbc71 100644
--- a/tree/library/dash/build.gradle
+++ b/tree/library/dash/build.gradle
@@ -33,6 +33,8 @@
}
}
+ sourceSets.test.assets.srcDir '../../testdata/src/test/assets/'
+
testOptions.unitTests.includeAndroidResources = true
}
@@ -40,6 +42,7 @@
implementation project(modulePrefix + 'library-core')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
index 88de846..2f2cc26 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
@@ -73,7 +73,7 @@
/* package */ final int id;
private final DashChunkSource.Factory chunkSourceFactory;
@Nullable private final TransferListener transferListener;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final long elapsedRealtimeOffsetMs;
private final LoaderErrorThrower manifestLoaderErrorThrower;
@@ -101,7 +101,7 @@
int periodIndex,
DashChunkSource.Factory chunkSourceFactory,
@Nullable TransferListener transferListener,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
long elapsedRealtimeOffsetMs,
@@ -480,7 +480,7 @@
}
private static Pair<TrackGroupArray, TrackGroupInfo[]> buildTrackGroups(
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
List<AdaptationSet> adaptationSets,
List<EventStream> eventStreams) {
int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets);
@@ -533,8 +533,9 @@
continue;
}
adaptationSetUsedFlags[i] = true;
- Descriptor adaptationSetSwitchingProperty = findAdaptationSetSwitchingProperty(
- adaptationSets.get(i).supplementalProperties);
+ @Nullable
+ Descriptor adaptationSetSwitchingProperty =
+ findAdaptationSetSwitchingProperty(adaptationSets.get(i).supplementalProperties);
if (adaptationSetSwitchingProperty == null) {
groupedAdaptationSetIndices[groupCount++] = new int[] {i};
} else {
@@ -597,7 +598,7 @@
}
private static int buildPrimaryAndEmbeddedTrackGroupInfos(
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
List<AdaptationSet> adaptationSets,
int[][] groupedAdaptationSetIndices,
int primaryGroupCount,
@@ -640,8 +641,11 @@
eventMessageTrackGroupIndex,
cea608TrackGroupIndex);
if (eventMessageTrackGroupIndex != C.INDEX_UNSET) {
- Format format = Format.createSampleFormat(firstAdaptationSet.id + ":emsg",
- MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
+ Format format =
+ new Format.Builder()
+ .setId(firstAdaptationSet.id + ":emsg")
+ .setSampleMimeType(MimeTypes.APPLICATION_EMSG)
+ .build();
trackGroups[eventMessageTrackGroupIndex] = new TrackGroup(format);
trackGroupInfos[eventMessageTrackGroupIndex] =
TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex);
@@ -659,8 +663,11 @@
TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos, int existingTrackGroupCount) {
for (int i = 0; i < eventStreams.size(); i++) {
EventStream eventStream = eventStreams.get(i);
- Format format = Format.createSampleFormat(eventStream.id(), MimeTypes.APPLICATION_EMSG, null,
- Format.NO_VALUE, null);
+ Format format =
+ new Format.Builder()
+ .setId(eventStream.id())
+ .setSampleMimeType(MimeTypes.APPLICATION_EMSG)
+ .build();
trackGroups[existingTrackGroupCount] = new TrackGroup(format);
trackGroupInfos[existingTrackGroupCount++] = TrackGroupInfo.mpdEventTrack(i);
}
@@ -738,6 +745,7 @@
return stream;
}
+ @Nullable
private static Descriptor findAdaptationSetSwitchingProperty(List<Descriptor> descriptors) {
for (int i = 0; i < descriptors.size(); i++) {
Descriptor descriptor = descriptors.get(i);
@@ -770,7 +778,7 @@
for (int j = 0; j < descriptors.size(); j++) {
Descriptor descriptor = descriptors.get(j);
if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri)) {
- String value = descriptor.value;
+ @Nullable String value = descriptor.value;
if (value == null) {
// There are embedded CEA-608 tracks, but service information is not declared.
return new Format[] {buildCea608TrackFormat(adaptationSet.id)};
@@ -802,20 +810,17 @@
}
private static Format buildCea608TrackFormat(
- int adaptationSetId, String language, int accessibilityChannel) {
- return Format.createTextSampleFormat(
+ int adaptationSetId, @Nullable String language, int accessibilityChannel) {
+ String id =
adaptationSetId
+ ":cea608"
- + (accessibilityChannel != Format.NO_VALUE ? ":" + accessibilityChannel : ""),
- MimeTypes.APPLICATION_CEA608,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- language,
- accessibilityChannel,
- /* drmInitData= */ null,
- Format.OFFSET_SAMPLE_RELATIVE,
- /* initializationData= */ null);
+ + (accessibilityChannel != Format.NO_VALUE ? ":" + accessibilityChannel : "");
+ return new Format.Builder()
+ .setId(id)
+ .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
+ .setLanguage(language)
+ .setAccessibilityChannel(accessibilityChannel)
+ .build();
}
// We won't assign the array to a variable that erases the generic type, and then write into it.
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
index f95e245..919997e 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSession;
@@ -63,6 +64,7 @@
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@@ -82,13 +84,13 @@
private final DashChunkSource.Factory chunkSourceFactory;
@Nullable private final DataSource.Factory manifestDataSourceFactory;
- private DrmSessionManager<?> drmSessionManager;
+ private DrmSessionManager drmSessionManager;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
private boolean livePresentationDelayOverridesManifest;
@Nullable private ParsingLoadable.Parser<? extends DashManifest> manifestParser;
- @Nullable private List<StreamKey> streamKeys;
+ private List<StreamKey> streamKeys;
@Nullable private Object tag;
/**
@@ -119,24 +121,28 @@
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
+ streamKeys = Collections.emptyList();
}
/**
- * Sets a tag for the media source which will be published in the {@link
- * com.google.android.exoplayer2.Timeline} of the source as {@link
- * com.google.android.exoplayer2.Timeline.Window#tag}.
- *
- * @param tag A tag for the media source.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
}
+ /**
+ * @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
+ * #createMediaSource(MediaItem)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public Factory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
- this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
+ this.streamKeys = streamKeys != null ? streamKeys : Collections.emptyList();
return this;
}
@@ -148,7 +154,7 @@
* @return This factory, for convenience.
*/
@Override
- public Factory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager) {
+ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager =
drmSessionManager != null
? drmSessionManager
@@ -180,6 +186,7 @@
return this;
}
+ /** @deprecated Use {@link #setLivePresentationDelayMs(long, boolean)} instead. */
@Deprecated
@SuppressWarnings("deprecation")
public Factory setLivePresentationDelayMs(long livePresentationDelayMs) {
@@ -251,7 +258,7 @@
*/
public DashMediaSource createMediaSource(DashManifest manifest) {
Assertions.checkArgument(!manifest.dynamic);
- if (streamKeys != null) {
+ if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
return new DashMediaSource(
@@ -300,24 +307,38 @@
return mediaSource;
}
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public DashMediaSource createMediaSource(Uri uri) {
+ return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
+ }
+
/**
* Returns a new {@link DashMediaSource} using the current parameters.
*
- * @param manifestUri The manifest {@link Uri}.
+ * @param mediaItem The media item of the dash stream.
* @return The new {@link DashMediaSource}.
+ * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
- public DashMediaSource createMediaSource(Uri manifestUri) {
+ public DashMediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
@Nullable ParsingLoadable.Parser<? extends DashManifest> manifestParser = this.manifestParser;
if (manifestParser == null) {
manifestParser = new DashManifestParser();
}
- if (streamKeys != null) {
+ List<StreamKey> streamKeys =
+ !mediaItem.playbackProperties.streamKeys.isEmpty()
+ ? mediaItem.playbackProperties.streamKeys
+ : this.streamKeys;
+ if (!streamKeys.isEmpty()) {
manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys);
}
return new DashMediaSource(
/* manifest= */ null,
- Assertions.checkNotNull(manifestUri),
+ mediaItem.playbackProperties.sourceUri,
manifestDataSourceFactory,
manifestParser,
chunkSourceFactory,
@@ -326,7 +347,7 @@
loadErrorHandlingPolicy,
livePresentationDelayMs,
livePresentationDelayOverridesManifest,
- tag);
+ mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
@Override
@@ -364,7 +385,7 @@
private final DataSource.Factory manifestDataSourceFactory;
private final DashChunkSource.Factory chunkSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final long livePresentationDelayMs;
private final boolean livePresentationDelayOverridesManifest;
@@ -580,7 +601,7 @@
@Nullable ParsingLoadable.Parser<? extends DashManifest> manifestParser,
DashChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long livePresentationDelayMs,
boolean livePresentationDelayOverridesManifest,
@@ -790,15 +811,18 @@
manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
manifestLoadEndTimestampMs = elapsedRealtimeMs;
- if (manifest.location != null) {
- synchronized (manifestUriLock) {
- // This condition checks that replaceManifestUri wasn't called between the start and end of
- // this load. If it was, we ignore the manifest location and prefer the manual replacement.
- @SuppressWarnings("ReferenceEquality")
- boolean isSameUriInstance = loadable.dataSpec.uri == manifestUri;
- if (isSameUriInstance) {
- manifestUri = manifest.location;
- }
+
+ synchronized (manifestUriLock) {
+ // Checks whether replaceManifestUri(Uri) was called to manually replace the URI between the
+ // start and end of this load. If it was then isSameUriInstance evaluates to false, and we
+ // prefer the manual replacement to one derived from the previous request.
+ @SuppressWarnings("ReferenceEquality")
+ boolean isSameUriInstance = loadable.dataSpec.uri == manifestUri;
+ if (isSameUriInstance) {
+ // Replace the manifest URI with one specified by a manifest Location element (if present),
+ // or with the final (possibly redirected) URI. This follows the recommendation in
+ // DASH-IF-IOP 4.3, section 3.2.15.3. See: https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf.
+ manifestUri = manifest.location != null ? manifest.location : loadable.getUri();
}
}
@@ -1323,14 +1347,17 @@
private final class ManifestCallback implements Loader.Callback<ParsingLoadable<DashManifest>> {
@Override
- public void onLoadCompleted(ParsingLoadable<DashManifest> loadable,
- long elapsedRealtimeMs, long loadDurationMs) {
+ public void onLoadCompleted(
+ ParsingLoadable<DashManifest> loadable, long elapsedRealtimeMs, long loadDurationMs) {
onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public void onLoadCanceled(ParsingLoadable<DashManifest> loadable,
- long elapsedRealtimeMs, long loadDurationMs, boolean released) {
+ public void onLoadCanceled(
+ ParsingLoadable<DashManifest> loadable,
+ long elapsedRealtimeMs,
+ long loadDurationMs,
+ boolean released) {
DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
@@ -1349,14 +1376,17 @@
private final class UtcTimestampCallback implements Loader.Callback<ParsingLoadable<Long>> {
@Override
- public void onLoadCompleted(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
- long loadDurationMs) {
+ public void onLoadCompleted(
+ ParsingLoadable<Long> loadable, long elapsedRealtimeMs, long loadDurationMs) {
onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs);
}
@Override
- public void onLoadCanceled(ParsingLoadable<Long> loadable, long elapsedRealtimeMs,
- long loadDurationMs, boolean released) {
+ public void onLoadCanceled(
+ ParsingLoadable<Long> loadable,
+ long elapsedRealtimeMs,
+ long loadDurationMs,
+ boolean released) {
DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs);
}
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java
index 74e4cb1..6d440b9 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java
@@ -46,6 +46,22 @@
public final class DashUtil {
/**
+ * Builds a {@link DataSpec} for a given {@link RangedUri} belonging to {@link Representation}.
+ *
+ * @param representation The {@link Representation} to which the request belongs.
+ * @param requestUri The {@link RangedUri} of the data to request.
+ * @return The {@link DataSpec}.
+ */
+ public static DataSpec buildDataSpec(Representation representation, RangedUri requestUri) {
+ return new DataSpec.Builder()
+ .setUri(requestUri.resolveUri(representation.baseUrl))
+ .setPosition(requestUri.start)
+ .setLength(requestUri.length)
+ .setKey(representation.getCacheKey())
+ .build();
+ }
+
+ /**
* Loads a DASH manifest.
*
* @param dataSource The {@link HttpDataSource} from which the manifest should be read.
@@ -53,8 +69,7 @@
* @return An instance of {@link DashManifest}.
* @throws IOException Thrown when there is an error while loading.
*/
- public static DashManifest loadManifest(DataSource dataSource, Uri uri)
- throws IOException {
+ public static DashManifest loadManifest(DataSource dataSource, Uri uri) throws IOException {
return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@@ -65,11 +80,10 @@
* @param period The {@link Period}.
* @return The loaded {@link DrmInitData}, or null if none is defined.
* @throws IOException Thrown when there is an error while loading.
- * @throws InterruptedException Thrown if the thread was interrupted.
*/
@Nullable
public static DrmInitData loadDrmInitData(DataSource dataSource, Period period)
- throws IOException, InterruptedException {
+ throws IOException {
int primaryTrackType = C.TRACK_TYPE_VIDEO;
Representation representation = getFirstRepresentation(period, primaryTrackType);
if (representation == null) {
@@ -83,7 +97,7 @@
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation);
return sampleFormat == null
? manifestFormat.drmInitData
- : sampleFormat.copyWithManifestFormatInfo(manifestFormat).drmInitData;
+ : sampleFormat.withManifestFormatInfo(manifestFormat).drmInitData;
}
/**
@@ -95,12 +109,10 @@
* @param representation The representation which initialization chunk belongs to.
* @return the sample {@link Format} of the given representation.
* @throws IOException Thrown when there is an error while loading.
- * @throws InterruptedException Thrown if the thread was interrupted.
*/
@Nullable
public static Format loadSampleFormat(
- DataSource dataSource, int trackType, Representation representation)
- throws IOException, InterruptedException {
+ DataSource dataSource, int trackType, Representation representation) throws IOException {
ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, trackType,
representation, false);
return extractorWrapper == null
@@ -119,12 +131,10 @@
* @return The {@link ChunkIndex} of the given representation, or null if no initialization or
* index data exists.
* @throws IOException Thrown when there is an error while loading.
- * @throws InterruptedException Thrown if the thread was interrupted.
*/
@Nullable
public static ChunkIndex loadChunkIndex(
- DataSource dataSource, int trackType, Representation representation)
- throws IOException, InterruptedException {
+ DataSource dataSource, int trackType, Representation representation) throws IOException {
ChunkExtractorWrapper extractorWrapper = loadInitializationData(dataSource, trackType,
representation, true);
return extractorWrapper == null ? null : (ChunkIndex) extractorWrapper.getSeekMap();
@@ -142,12 +152,11 @@
* @return A {@link ChunkExtractorWrapper} for the {@code representation}, or null if no
* initialization or (if requested) index data exists.
* @throws IOException Thrown when there is an error while loading.
- * @throws InterruptedException Thrown if the thread was interrupted.
*/
@Nullable
private static ChunkExtractorWrapper loadInitializationData(
DataSource dataSource, int trackType, Representation representation, boolean loadIndex)
- throws IOException, InterruptedException {
+ throws IOException {
RangedUri initializationUri = representation.getInitializationUri();
if (initializationUri == null) {
return null;
@@ -173,11 +182,13 @@
return extractorWrapper;
}
- private static void loadInitializationData(DataSource dataSource,
- Representation representation, ChunkExtractorWrapper extractorWrapper, RangedUri requestUri)
- throws IOException, InterruptedException {
- DataSpec dataSpec = new DataSpec(requestUri.resolveUri(representation.baseUrl),
- requestUri.start, requestUri.length, representation.getCacheKey());
+ private static void loadInitializationData(
+ DataSource dataSource,
+ Representation representation,
+ ChunkExtractorWrapper extractorWrapper,
+ RangedUri requestUri)
+ throws IOException {
+ DataSpec dataSpec = DashUtil.buildDataSpec(representation, requestUri);
InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec,
representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */,
extractorWrapper);
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
index c4a0511..e03ade2 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
@@ -487,20 +487,19 @@
Object trackSelectionData,
RangedUri initializationUri,
RangedUri indexUri) {
+ Representation representation = representationHolder.representation;
RangedUri requestUri;
- String baseUrl = representationHolder.representation.baseUrl;
if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
- requestUri = initializationUri.attemptMerge(indexUri, baseUrl);
+ requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrl);
if (requestUri == null) {
requestUri = initializationUri;
}
} else {
requestUri = indexUri;
}
- DataSpec dataSpec = new DataSpec(requestUri.resolveUri(baseUrl), requestUri.start,
- requestUri.length, representationHolder.representation.getCacheKey());
+ DataSpec dataSpec = DashUtil.buildDataSpec(representation, requestUri);
return new InitializationChunk(dataSource, dataSpec, trackFormat,
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
}
@@ -521,15 +520,14 @@
String baseUrl = representation.baseUrl;
if (representationHolder.extractorWrapper == null) {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
- DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
- segmentUri.start, segmentUri.length, representation.getCacheKey());
+ DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri);
return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackType, trackFormat);
} else {
int segmentCount = 1;
for (int i = 1; i < maxSegmentCount; i++) {
RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i);
- RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl);
+ @Nullable RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl);
if (mergedSegmentUri == null) {
// Unable to merge segment fetches because the URIs do not merge.
break;
@@ -543,8 +541,7 @@
periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs
? periodDurationUs
: C.TIME_UNSET;
- DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
- segmentUri.start, segmentUri.length, representation.getCacheKey());
+ DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri);
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(
dataSource,
@@ -588,11 +585,8 @@
@Override
public DataSpec getDataSpec() {
checkInBounds();
- Representation representation = representationHolder.representation;
RangedUri segmentUri = representationHolder.getSegmentUrl(getCurrentIndex());
- Uri resolvedUri = segmentUri.resolveUri(representation.baseUrl);
- String cacheKey = representation.getCacheKey();
- return new DataSpec(resolvedUri, segmentUri.start, segmentUri.length, cacheKey);
+ return DashUtil.buildDataSpec(representationHolder.representation, segmentUri);
}
@Override
@@ -778,10 +772,6 @@
|| mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
}
- private static boolean mimeTypeIsRawText(String mimeType) {
- return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType);
- }
-
private static @Nullable ChunkExtractorWrapper createExtractorWrapper(
int trackType,
Representation representation,
@@ -789,12 +779,15 @@
List<Format> closedCaptionFormats,
@Nullable TrackOutput playerEmsgTrackOutput) {
String containerMimeType = representation.format.containerMimeType;
- if (mimeTypeIsRawText(containerMimeType)) {
- return null;
- }
Extractor extractor;
- if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
- extractor = new RawCcExtractor(representation.format);
+ if (MimeTypes.isText(containerMimeType)) {
+ if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
+ // RawCC is special because it's a text specific container format.
+ extractor = new RawCcExtractor(representation.format);
+ } else {
+ // All other text types are raw formats that do not need an extractor.
+ return null;
+ }
} else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES);
} else {
@@ -804,10 +797,12 @@
}
extractor =
new FragmentedMp4Extractor(
- flags, null, null, null, closedCaptionFormats, playerEmsgTrackOutput);
+ flags,
+ /* timestampAdjuster= */ null,
+ /* sideloadedTrack= */ null,
+ closedCaptionFormats,
+ playerEmsgTrackOutput);
}
- // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
- // as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
return new ChunkExtractorWrapper(extractor, trackType, representation.format);
}
}
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
index 3b52e07..504b2f4 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
@@ -25,7 +25,6 @@
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
@@ -35,6 +34,8 @@
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.DataReader;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
@@ -284,7 +285,11 @@
private final MetadataInputBuffer buffer;
/* package */ PlayerTrackEmsgHandler(Allocator allocator) {
- this.sampleQueue = new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager());
+ this.sampleQueue =
+ new SampleQueue(
+ allocator,
+ DrmSessionManager.getDummyDrmSessionManager(),
+ new MediaSourceEventDispatcher());
formatHolder = new FormatHolder();
buffer = new MetadataInputBuffer();
}
@@ -295,8 +300,8 @@
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput)
+ throws IOException {
return sampleQueue.sampleData(input, length, allowEndOfInput);
}
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
index b107be4..1ceeb31 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
@@ -222,10 +222,11 @@
protected Pair<Period, Long> parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs)
throws XmlPullParserException, IOException {
- String id = xpp.getAttributeValue(null, "id");
+ @Nullable String id = xpp.getAttributeValue(null, "id");
long startMs = parseDuration(xpp, "start", defaultStartMs);
long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET);
- SegmentBase segmentBase = null;
+ @Nullable SegmentBase segmentBase = null;
+ @Nullable Descriptor assetIdentifier = null;
List<AdaptationSet> adaptationSets = new ArrayList<>();
List<EventStream> eventStreams = new ArrayList<>();
boolean seenFirstBaseUrl = false;
@@ -246,17 +247,24 @@
segmentBase = parseSegmentList(xpp, null, durationMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, null, Collections.emptyList(), durationMs);
+ } else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
+ assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "Period"));
- return Pair.create(buildPeriod(id, startMs, adaptationSets, eventStreams), durationMs);
+ return Pair.create(
+ buildPeriod(id, startMs, adaptationSets, eventStreams, assetIdentifier), durationMs);
}
- protected Period buildPeriod(String id, long startMs, List<AdaptationSet> adaptationSets,
- List<EventStream> eventStreams) {
- return new Period(id, startMs, adaptationSets, eventStreams);
+ protected Period buildPeriod(
+ @Nullable String id,
+ long startMs,
+ List<AdaptationSet> adaptationSets,
+ List<EventStream> eventStreams,
+ @Nullable Descriptor assetIdentifier) {
+ return new Period(id, startMs, adaptationSets, eventStreams, assetIdentifier);
}
// AdaptationSet parsing.
@@ -329,8 +337,9 @@
supplementalProperties,
segmentBase,
periodDurationMs);
- contentType = checkContentTypeConsistency(contentType,
- getContentType(representationInfo.format));
+ contentType =
+ checkContentTypeConsistency(
+ contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
representationInfos.add(representationInfo);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase);
@@ -381,20 +390,6 @@
: C.TRACK_TYPE_UNKNOWN;
}
- protected int getContentType(Format format) {
- String sampleMimeType = format.sampleMimeType;
- if (TextUtils.isEmpty(sampleMimeType)) {
- return C.TRACK_TYPE_UNKNOWN;
- } else if (MimeTypes.isVideo(sampleMimeType)) {
- return C.TRACK_TYPE_VIDEO;
- } else if (MimeTypes.isAudio(sampleMimeType)) {
- return C.TRACK_TYPE_AUDIO;
- } else if (mimeTypeIsRawText(sampleMimeType)) {
- return C.TRACK_TYPE_TEXT;
- }
- return C.TRACK_TYPE_UNKNOWN;
- }
-
/**
* Parses a ContentProtection element.
*
@@ -589,76 +584,40 @@
List<Descriptor> accessibilityDescriptors,
@Nullable String codecs,
List<Descriptor> supplementalProperties) {
- String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
+ @Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
+ if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) {
+ sampleMimeType = parseEac3SupplementalProperties(supplementalProperties);
+ }
@C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors);
@C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors);
roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors);
- if (sampleMimeType != null) {
- if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) {
- sampleMimeType = parseEac3SupplementalProperties(supplementalProperties);
+
+ Format.Builder formatBuilder =
+ new Format.Builder()
+ .setId(id)
+ .setContainerMimeType(containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setCodecs(codecs)
+ .setPeakBitrate(bitrate)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setLanguage(language);
+
+ if (MimeTypes.isVideo(sampleMimeType)) {
+ formatBuilder.setWidth(width).setHeight(height).setFrameRate(frameRate);
+ } else if (MimeTypes.isAudio(sampleMimeType)) {
+ formatBuilder.setChannelCount(audioChannels).setSampleRate(audioSamplingRate);
+ } else if (MimeTypes.isText(sampleMimeType)) {
+ int accessibilityChannel = Format.NO_VALUE;
+ if (MimeTypes.APPLICATION_CEA608.equals(sampleMimeType)) {
+ accessibilityChannel = parseCea608AccessibilityChannel(accessibilityDescriptors);
+ } else if (MimeTypes.APPLICATION_CEA708.equals(sampleMimeType)) {
+ accessibilityChannel = parseCea708AccessibilityChannel(accessibilityDescriptors);
}
- if (MimeTypes.isVideo(sampleMimeType)) {
- return Format.createVideoContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- bitrate,
- width,
- height,
- frameRate,
- /* initializationData= */ null,
- selectionFlags,
- roleFlags);
- } else if (MimeTypes.isAudio(sampleMimeType)) {
- return Format.createAudioContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- bitrate,
- audioChannels,
- audioSamplingRate,
- /* initializationData= */ null,
- selectionFlags,
- roleFlags,
- language);
- } else if (mimeTypeIsRawText(sampleMimeType)) {
- int accessibilityChannel;
- if (MimeTypes.APPLICATION_CEA608.equals(sampleMimeType)) {
- accessibilityChannel = parseCea608AccessibilityChannel(accessibilityDescriptors);
- } else if (MimeTypes.APPLICATION_CEA708.equals(sampleMimeType)) {
- accessibilityChannel = parseCea708AccessibilityChannel(accessibilityDescriptors);
- } else {
- accessibilityChannel = Format.NO_VALUE;
- }
- return Format.createTextContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- roleFlags,
- language,
- accessibilityChannel);
- }
+ formatBuilder.setAccessibilityChannel(accessibilityChannel);
}
- return Format.createContainerFormat(
- id,
- /* label= */ null,
- containerMimeType,
- sampleMimeType,
- codecs,
- bitrate,
- selectionFlags,
- roleFlags,
- language);
+
+ return formatBuilder.build();
}
protected Representation buildRepresentation(
@@ -667,24 +626,25 @@
@Nullable String extraDrmSchemeType,
ArrayList<SchemeData> extraDrmSchemeDatas,
ArrayList<Descriptor> extraInbandEventStreams) {
- Format format = representationInfo.format;
+ Format.Builder formatBuilder = representationInfo.format.buildUpon();
if (label != null) {
- format = format.copyWithLabel(label);
+ formatBuilder.setLabel(label);
}
- String drmSchemeType = representationInfo.drmSchemeType != null
- ? representationInfo.drmSchemeType : extraDrmSchemeType;
+ @Nullable String drmSchemeType = representationInfo.drmSchemeType;
+ if (drmSchemeType == null) {
+ drmSchemeType = extraDrmSchemeType;
+ }
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
drmSchemeDatas.addAll(extraDrmSchemeDatas);
if (!drmSchemeDatas.isEmpty()) {
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
- DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas);
- format = format.copyWithDrmInitData(drmInitData);
+ formatBuilder.setDrmInitData(new DrmInitData(drmSchemeType, drmSchemeDatas));
}
ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams;
inbandEventStreams.addAll(extraInbandEventStreams);
return Representation.newInstance(
representationInfo.revisionId,
- format,
+ formatBuilder.build(),
representationInfo.baseUrl,
representationInfo.segmentBase,
inbandEventStreams);
@@ -709,7 +669,7 @@
indexLength = Long.parseLong(indexRange[1]) - indexStart + 1;
}
- RangedUri initialization = parent != null ? parent.initialization : null;
+ @Nullable RangedUri initialization = parent != null ? parent.initialization : null;
do {
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) {
@@ -1337,44 +1297,20 @@
return MimeTypes.getAudioMediaMimeType(codecs);
} else if (MimeTypes.isVideo(containerMimeType)) {
return MimeTypes.getVideoMediaMimeType(codecs);
- } else if (mimeTypeIsRawText(containerMimeType)) {
+ } else if (MimeTypes.isText(containerMimeType)) {
+ if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
+ // RawCC is special because it's a text specific container format.
+ return MimeTypes.getTextMediaMimeType(codecs);
+ }
+ // All other text types are raw formats.
return containerMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) {
- if (codecs != null) {
- if (codecs.startsWith("stpp")) {
- return MimeTypes.APPLICATION_TTML;
- } else if (codecs.startsWith("wvtt")) {
- return MimeTypes.APPLICATION_MP4VTT;
- }
- }
- } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
- if (codecs != null) {
- if (codecs.contains("cea708")) {
- return MimeTypes.APPLICATION_CEA708;
- } else if (codecs.contains("eia608") || codecs.contains("cea608")) {
- return MimeTypes.APPLICATION_CEA608;
- }
- }
- return null;
+ return MimeTypes.getMediaMimeType(codecs);
}
return null;
}
/**
- * Returns whether a mimeType is a text sample mimeType.
- *
- * @param mimeType The mimeType.
- * @return Whether the mimeType is a text sample mimeType.
- */
- private static boolean mimeTypeIsRawText(@Nullable String mimeType) {
- return MimeTypes.isText(mimeType)
- || MimeTypes.APPLICATION_TTML.equals(mimeType)
- || MimeTypes.APPLICATION_MP4VTT.equals(mimeType)
- || MimeTypes.APPLICATION_CEA708.equals(mimeType)
- || MimeTypes.APPLICATION_CEA608.equals(mimeType);
- }
-
- /**
* Checks two languages for consistency, returning the consistent language, or throwing an {@link
* IllegalStateException} if the languages are inconsistent.
*
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java
index 18614ca..b472aed 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java
@@ -45,13 +45,16 @@
*/
public final List<EventStream> eventStreams;
+ /** The asset identifier for this period, if one exists */
+ @Nullable public final Descriptor assetIdentifier;
+
/**
* @param id The period identifier. May be null.
* @param startMs The start time of the period in milliseconds.
* @param adaptationSets The adaptation sets belonging to the period.
*/
public Period(@Nullable String id, long startMs, List<AdaptationSet> adaptationSets) {
- this(id, startMs, adaptationSets, Collections.emptyList());
+ this(id, startMs, adaptationSets, Collections.emptyList(), /* assetIdentifier= */ null);
}
/**
@@ -62,10 +65,27 @@
*/
public Period(@Nullable String id, long startMs, List<AdaptationSet> adaptationSets,
List<EventStream> eventStreams) {
+ this(id, startMs, adaptationSets, eventStreams, /* assetIdentifier= */ null);
+ }
+
+ /**
+ * @param id The period identifier. May be null.
+ * @param startMs The start time of the period in milliseconds.
+ * @param adaptationSets The adaptation sets belonging to the period.
+ * @param eventStreams The {@link EventStream}s belonging to the period.
+ * @param assetIdentifier The asset identifier for this period
+ */
+ public Period(
+ @Nullable String id,
+ long startMs,
+ List<AdaptationSet> adaptationSets,
+ List<EventStream> eventStreams,
+ @Nullable Descriptor assetIdentifier) {
this.id = id;
this.startMs = startMs;
this.adaptationSets = Collections.unmodifiableList(adaptationSets);
this.eventStreams = Collections.unmodifiableList(eventStreams);
+ this.assetIdentifier = assetIdentifier;
}
/**
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java
index 2754a33..7f76e65 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java
@@ -153,7 +153,7 @@
private static void addSegment(
long startTimeUs, String baseUrl, RangedUri rangedUri, ArrayList<Segment> out) {
DataSpec dataSpec =
- new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start, rangedUri.length, null);
+ new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start, rangedUri.length);
out.add(new Segment(startTimeUs, dataSpec));
}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
index f7d7b96..92aa49d 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
@@ -186,50 +186,33 @@
}
private static Format createVideoFormat(int bitrate) {
- return Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.VIDEO_MP4,
- MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setPeakBitrate(bitrate)
+ .build();
}
private static Representation createAudioRepresentation(int bitrate) {
+ Format format =
+ new Format.Builder()
+ .setContainerMimeType(MimeTypes.AUDIO_MP4)
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setPeakBitrate(bitrate)
+ .build();
return Representation.newInstance(
- /* revisionId= */ 0,
- Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.AUDIO_MP4,
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null),
- /* baseUrl= */ "",
- new SingleSegmentBase());
+ /* revisionId= */ 0, format, /* baseUrl= */ "", new SingleSegmentBase());
}
private static Representation createTextRepresentation(String language) {
+ Format format =
+ new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_MP4)
+ .setSampleMimeType(MimeTypes.TEXT_VTT)
+ .setLanguage(language)
+ .build();
return Representation.newInstance(
- /* revisionId= */ 0,
- Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.APPLICATION_MP4,
- MimeTypes.TEXT_VTT,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- language),
- /* baseUrl= */ "",
- new SingleSegmentBase());
+ /* revisionId= */ 0, format, /* baseUrl= */ "", new SingleSegmentBase());
}
private static Descriptor createSwitchDescriptor(int... ids) {
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java
index 2aca8c3..3c8952f 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java
@@ -32,7 +32,7 @@
public final class DashMediaSourceTest {
@Test
- public void testIso8601ParserParse() throws IOException {
+ public void iso8601ParserParse() throws IOException {
DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser();
// UTC.
assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37Z");
@@ -58,7 +58,7 @@
}
@Test
- public void testIso8601ParserParseMissingTimezone() throws IOException {
+ public void iso8601ParserParseMissingTimezone() throws IOException {
DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser();
try {
assertParseStringToLong(0, parser, "2017-12-04T10:01:37");
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
index 6e769b7..28f15b2 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
@@ -37,28 +37,28 @@
public final class DashUtilTest {
@Test
- public void testLoadDrmInitDataFromManifest() throws Exception {
- Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData())));
+ public void loadDrmInitDataFromManifest() throws Exception {
+ Period period = newPeriod(newAdaptationSet(newRepresentation(newDrmInitData())));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isEqualTo(newDrmInitData());
}
@Test
- public void testLoadDrmInitDataMissing() throws Exception {
- Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */)));
+ public void loadDrmInitDataMissing() throws Exception {
+ Period period = newPeriod(newAdaptationSet(newRepresentation(null /* no init data */)));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
}
@Test
- public void testLoadDrmInitDataNoRepresentations() throws Exception {
- Period period = newPeriod(newAdaptationSets(/* no representation */ ));
+ public void loadDrmInitDataNoRepresentations() throws Exception {
+ Period period = newPeriod(newAdaptationSet(/* no representation */ ));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
}
@Test
- public void testLoadDrmInitDataNoAdaptationSets() throws Exception {
+ public void loadDrmInitDataNoAdaptationSets() throws Exception {
Period period = newPeriod(/* no adaptation set */ );
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
@@ -68,29 +68,17 @@
return new Period("", 0, Arrays.asList(adaptationSets));
}
- private static AdaptationSet newAdaptationSets(Representation... representations) {
+ private static AdaptationSet newAdaptationSet(Representation... representations) {
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null, null);
}
- private static Representation newRepresentations(DrmInitData drmInitData) {
+ private static Representation newRepresentation(DrmInitData drmInitData) {
Format format =
- Format.createVideoContainerFormat(
- "id",
- "label",
- MimeTypes.VIDEO_MP4,
- MimeTypes.VIDEO_H264,
- /* codecs= */ "",
- /* metadata= */ null,
- Format.NO_VALUE,
- /* width= */ 1024,
- /* height= */ 768,
- Format.NO_VALUE,
- /* initializationData= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0);
- if (drmInitData != null) {
- format = format.copyWithDrmInitData(drmInitData);
- }
+ new Format.Builder()
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setDrmInitData(drmInitData)
+ .build();
return Representation.newInstance(0, format, "", new SingleSegmentBase());
}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultMediaSourceFactoryTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultMediaSourceFactoryTest.java
new file mode 100644
index 0000000..073bd82
--- /dev/null
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultMediaSourceFactoryTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source.dash;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.util.MimeTypes;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for creating DASH media sources with the {@link DefaultMediaSourceFactory}. */
+@RunWith(AndroidJUnit4.class)
+public class DefaultMediaSourceFactoryTest {
+
+ private static final String URI_MEDIA = "http://exoplayer.dev/video";
+
+ @Test
+ public void createMediaSource_withMimeType_dashSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(DashMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withTag_tagInSource() {
+ Object tag = new Object();
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_MPD)
+ .setTag(tag)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource.getTag()).isEqualTo(tag);
+ }
+
+ @Test
+ public void createMediaSource_withPath_dashSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.mpd").build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(DashMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withNull_usesNonNullDefaults() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.mpd").build();
+
+ MediaSource mediaSource =
+ defaultMediaSourceFactory
+ .setDrmSessionManager(null)
+ .setDrmHttpDataSourceFactory(null)
+ .setLoadErrorHandlingPolicy(null)
+ .createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isNotNull();
+ }
+
+ @Test
+ public void getSupportedTypes_dashModule_containsTypeDash() {
+ int[] supportedTypes =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext())
+ .getSupportedTypes();
+
+ assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER, C.TYPE_DASH);
+ }
+}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java
index b946931..11f4b37 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java
@@ -37,8 +37,11 @@
private static final String SCHEME_ID = "urn:test";
private static final String VALUE = "123";
- private static final Format FORMAT = Format.createSampleFormat("urn:test/123",
- MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
+ private static final Format FORMAT =
+ new Format.Builder()
+ .setId("urn:test/123")
+ .setSampleMimeType(MimeTypes.APPLICATION_EMSG)
+ .build();
private static final byte[] MESSAGE_DATA = new byte[] {1, 2, 3, 4};
private static final long DURATION_MS = 3000;
private static final long TIME_SCALE = 1000;
@@ -59,7 +62,7 @@
* return format for the first call.
*/
@Test
- public void testReadDataReturnFormatForFirstRead() {
+ public void readDataReturnFormatForFirstRead() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
@@ -70,11 +73,11 @@
}
/**
- * Tests that a non-dynamic {@link EventSampleStream} will return a buffer with
- * {@link C#BUFFER_FLAG_END_OF_STREAM} when trying to read sample out-of-bound.
+ * Tests that a non-dynamic {@link EventSampleStream} will return a buffer with {@link
+ * C#BUFFER_FLAG_END_OF_STREAM} when trying to read sample out-of-bound.
*/
@Test
- public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForNonDynamicEventSampleStream() {
+ public void readDataOutOfBoundReturnEndOfStreamAfterFormatForNonDynamicEventSampleStream() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
@@ -87,11 +90,11 @@
}
/**
- * Tests that a dynamic {@link EventSampleStream} will return {@link C#RESULT_NOTHING_READ}
- * when trying to read sample out-of-bound.
+ * Tests that a dynamic {@link EventSampleStream} will return {@link C#RESULT_NOTHING_READ} when
+ * trying to read sample out-of-bound.
*/
@Test
- public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForDynamicEventSampleStream() {
+ public void readDataOutOfBoundReturnEndOfStreamAfterFormatForDynamicEventSampleStream() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, true);
@@ -107,7 +110,7 @@
* return sample data after the first call.
*/
@Test
- public void testReadDataReturnDataAfterFormat() {
+ public void readDataReturnDataAfterFormat() {
long presentationTimeUs = 1000000;
EventMessage eventMessage = newEventMessageWithId(1);
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
@@ -123,12 +126,12 @@
}
/**
- * Tests that {@link EventSampleStream#skipData(long)} will skip until the given position, and
- * the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from that position.
+ * Tests that {@link EventSampleStream#skipData(long)} will skip until the given position, and the
+ * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will
+ * return sample data from that position.
*/
@Test
- public void testSkipDataThenReadDataReturnDataFromSkippedPosition() {
+ public void skipDataThenReadDataReturnDataFromSkippedPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
EventMessage eventMessage1 = newEventMessageWithId(1);
@@ -154,7 +157,7 @@
* will return sample data from that position.
*/
@Test
- public void testSeekToUsThenReadDataReturnDataFromSeekPosition() {
+ public void seekToUsThenReadDataReturnDataFromSeekPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
EventMessage eventMessage1 = newEventMessageWithId(1);
@@ -175,12 +178,12 @@
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
- * underlying event stream, but keep the read timestamp, so the next
- * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from after the last read sample timestamp.
+ * underlying event stream, but keep the read timestamp, so the next {@link
+ * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample
+ * data from after the last read sample timestamp.
*/
@Test
- public void testUpdateEventStreamContinueToReadAfterLastReadSamplePresentationTime() {
+ public void updateEventStreamContinueToReadAfterLastReadSamplePresentationTime() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
@@ -209,12 +212,12 @@
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
- * underlying event stream, but keep the timestamp the stream has skipped to, so the next
- * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from the skipped position.
+ * underlying event stream, but keep the timestamp the stream has skipped to, so the next {@link
+ * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample
+ * data from the skipped position.
*/
@Test
- public void testSkipDataThenUpdateStreamContinueToReadFromSkippedPosition() {
+ public void skipDataThenUpdateStreamContinueToReadFromSkippedPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
@@ -240,14 +243,14 @@
}
/**
- * Tests that {@link EventSampleStream#skipData(long)} will only skip to the point right after
- * it last event. A following {@link EventSampleStream#updateEventStream(EventStream, boolean)}
- * will update the underlying event stream and keep the timestamp the stream has skipped to, so
- * the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from the skipped position.
+ * Tests that {@link EventSampleStream#skipData(long)} will only skip to the point right after it
+ * last event. A following {@link EventSampleStream#updateEventStream(EventStream, boolean)} will
+ * update the underlying event stream and keep the timestamp the stream has skipped to, so the
+ * next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will
+ * return sample data from the skipped position.
*/
@Test
- public void testSkipDataThenUpdateStreamContinueToReadDoNotSkippedMoreThanAvailable() {
+ public void skipDataThenUpdateStreamContinueToReadDoNotSkippedMoreThanAvailable() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
@@ -276,12 +279,12 @@
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
- * underlying event stream, but keep the timestamp the stream has seek to, so the next
- * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from the seek position.
+ * underlying event stream, but keep the timestamp the stream has seek to, so the next {@link
+ * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample
+ * data from the seek position.
*/
@Test
- public void testSeekToUsThenUpdateStreamContinueToReadFromSeekPosition() {
+ public void seekToUsThenUpdateStreamContinueToReadFromSeekPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
@@ -308,12 +311,12 @@
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
- * underlying event stream, but keep the timestamp the stream has seek to, so the next
- * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
- * will return sample data from the seek position.
+ * underlying event stream, but keep the timestamp the stream has seek to, so the next {@link
+ * EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call will return sample
+ * data from the seek position.
*/
@Test
- public void testSeekToThenUpdateStreamContinueToReadFromSeekPositionEvenSeekMoreThanAvailable() {
+ public void seekToThenUpdateStreamContinueToReadFromSeekPositionEvenSeekMoreThanAvailable() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
index 390a18d..37b3046 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
@@ -25,6 +26,7 @@
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer2.testutil.TestUtil;
+import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.io.StringReader;
@@ -40,11 +42,13 @@
@RunWith(AndroidJUnit4.class)
public class DashManifestParserTest {
- private static final String SAMPLE_MPD = "sample_mpd";
- private static final String SAMPLE_MPD_UNKNOWN_MIME_TYPE = "sample_mpd_unknown_mime_type";
- private static final String SAMPLE_MPD_SEGMENT_TEMPLATE = "sample_mpd_segment_template";
- private static final String SAMPLE_MPD_EVENT_STREAM = "sample_mpd_event_stream";
- private static final String SAMPLE_MPD_LABELS = "sample_mpd_labels";
+ private static final String SAMPLE_MPD = "mpd/sample_mpd";
+ private static final String SAMPLE_MPD_UNKNOWN_MIME_TYPE = "mpd/sample_mpd_unknown_mime_type";
+ private static final String SAMPLE_MPD_SEGMENT_TEMPLATE = "mpd/sample_mpd_segment_template";
+ private static final String SAMPLE_MPD_EVENT_STREAM = "mpd/sample_mpd_event_stream";
+ private static final String SAMPLE_MPD_LABELS = "mpd/sample_mpd_labels";
+ private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "mpd/sample_mpd_asset_identifier";
+ private static final String SAMPLE_MPD_TEXT = "mpd/sample_mpd_text";
private static final String NEXT_TAG_NAME = "Next";
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
@@ -65,15 +69,15 @@
@Test
public void parseMediaPresentationDescription_segmentTemplate() throws IOException {
DashManifestParser parser = new DashManifestParser();
- DashManifest mpd =
+ DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(), SAMPLE_MPD_SEGMENT_TEMPLATE));
- assertThat(mpd.getPeriodCount()).isEqualTo(1);
+ assertThat(manifest.getPeriodCount()).isEqualTo(1);
- Period period = mpd.getPeriod(0);
+ Period period = manifest.getPeriod(0);
assertThat(period).isNotNull();
assertThat(period.adaptationSets).hasSize(2);
@@ -97,13 +101,13 @@
@Test
public void parseMediaPresentationDescription_eventStream() throws IOException {
DashManifestParser parser = new DashManifestParser();
- DashManifest mpd =
+ DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(), SAMPLE_MPD_EVENT_STREAM));
- Period period = mpd.getPeriod(0);
+ Period period = manifest.getPeriod(0);
assertThat(period.eventStreams).hasSize(3);
// assert text-only event stream
@@ -167,14 +171,14 @@
@Test
public void parseMediaPresentationDescription_programInformation() throws IOException {
DashManifestParser parser = new DashManifestParser();
- DashManifest mpd =
+ DashManifest manifest =
parser.parse(
Uri.parse("Https://example.com/test.mpd"),
TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD));
ProgramInformation expectedProgramInformation =
new ProgramInformation(
"MediaTitle", "MediaSource", "MediaCopyright", "www.example.com", "enUs");
- assertThat(mpd.programInformation).isEqualTo(expectedProgramInformation);
+ assertThat(manifest.programInformation).isEqualTo(expectedProgramInformation);
}
@Test
@@ -193,6 +197,35 @@
}
@Test
+ public void parseMediaPresentationDescription_text() throws IOException {
+ DashManifestParser parser = new DashManifestParser();
+ DashManifest manifest =
+ parser.parse(
+ Uri.parse("Https://example.com/test.mpd"),
+ TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TEXT));
+
+ List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
+
+ Format format = adaptationSets.get(0).representations.get(0).format;
+ assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_RAWCC);
+ assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA608);
+ assertThat(format.codecs).isEqualTo("cea608");
+ assertThat(adaptationSets.get(0).type).isEqualTo(C.TRACK_TYPE_TEXT);
+
+ format = adaptationSets.get(1).representations.get(0).format;
+ assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_MP4);
+ assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(format.codecs).isEqualTo("stpp.ttml.im1t");
+ assertThat(adaptationSets.get(1).type).isEqualTo(C.TRACK_TYPE_TEXT);
+
+ format = adaptationSets.get(2).representations.get(0).format;
+ assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(format.codecs).isNull();
+ assertThat(adaptationSets.get(2).type).isEqualTo(C.TRACK_TYPE_TEXT);
+ }
+
+ @Test
public void parseSegmentTimeline_repeatCount() throws Exception {
DashManifestParser parser = new DashManifestParser();
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
@@ -377,6 +410,27 @@
.isEqualTo(Format.NO_VALUE);
}
+ @Test
+ public void parsePeriodAssetIdentifier() throws IOException {
+ DashManifestParser parser = new DashManifestParser();
+ DashManifest manifest =
+ parser.parse(
+ Uri.parse("https://example.com/test.mpd"),
+ TestUtil.getInputStream(
+ ApplicationProvider.getApplicationContext(), SAMPLE_MPD_ASSET_IDENTIFIER));
+
+ assertThat(manifest.getPeriodCount()).isEqualTo(1);
+
+ Period period = manifest.getPeriod(0);
+ assertThat(period).isNotNull();
+ @Nullable Descriptor assetIdentifier = period.assetIdentifier;
+ assertThat(assetIdentifier).isNotNull();
+
+ assertThat(assetIdentifier.schemeIdUri).isEqualTo("urn:org:dashif:asset-id:2013");
+ assertThat(assetIdentifier.value).isEqualTo("md:cid:EIDR:10.5240%2f0EFB-02CD-126E-8092-1E49-W");
+ assertThat(assetIdentifier.id).isEqualTo("uniqueId");
+ }
+
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
index a336602..b063c54 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
@@ -35,10 +35,10 @@
private static final UtcTimingElement DUMMY_UTC_TIMING = new UtcTimingElement("", "");
private static final SingleSegmentBase DUMMY_SEGMENT_BASE = new SingleSegmentBase();
- private static final Format DUMMY_FORMAT = Format.createSampleFormat("", "", 0);
+ private static final Format DUMMY_FORMAT = new Format.Builder().build();
@Test
- public void testCopy() throws Exception {
+ public void copy() {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest =
newDashManifest(
@@ -97,7 +97,7 @@
}
@Test
- public void testCopySameAdaptationIndexButDifferentPeriod() throws Exception {
+ public void copySameAdaptationIndexButDifferentPeriod() {
Representation[][][] representations = newRepresentations(2, 1, 1);
DashManifest sourceManifest =
newDashManifest(
@@ -117,7 +117,7 @@
}
@Test
- public void testCopySkipPeriod() throws Exception {
+ public void copySkipPeriod() {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest =
newDashManifest(
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java
index be18662..44af227 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java
@@ -31,7 +31,7 @@
private static final String FULL_URI = BASE_URI + PARTIAL_URI;
@Test
- public void testMerge() {
+ public void merge() {
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, 10);
RangedUri expected = new RangedUri(FULL_URI, 0, 20);
@@ -39,7 +39,7 @@
}
@Test
- public void testMergeUnbounded() {
+ public void mergeUnbounded() {
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, C.LENGTH_UNSET);
RangedUri expected = new RangedUri(FULL_URI, 0, C.LENGTH_UNSET);
@@ -47,7 +47,7 @@
}
@Test
- public void testNonMerge() {
+ public void nonMerge() {
// A and B do not overlap, so should not merge
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 11, 10);
@@ -70,7 +70,7 @@
}
@Test
- public void testMergeWithBaseUri() {
+ public void mergeWithBaseUri() {
RangedUri rangeA = new RangedUri(PARTIAL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, 10);
RangedUri expected = new RangedUri(FULL_URI, 0, 20);
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java
index c65c2d0..6736840 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java
@@ -27,7 +27,7 @@
public class UrlTemplateTest {
@Test
- public void testRealExamples() {
+ public void realExamples() {
String template = "QualityLevels($Bandwidth$)/Fragments(video=$Time$,format=mpd-time-csf)";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
@@ -45,7 +45,7 @@
}
@Test
- public void testFull() {
+ public void full() {
String template = "$Bandwidth$_a_$RepresentationID$_b_$Time$_c_$Number$";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
@@ -53,7 +53,7 @@
}
@Test
- public void testFullWithDollarEscaping() {
+ public void fullWithDollarEscaping() {
String template = "$$$Bandwidth$$$_a$$_$RepresentationID$_b_$Time$_c_$Number$$$";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
@@ -61,7 +61,7 @@
}
@Test
- public void testInvalidSubstitution() {
+ public void invalidSubstitution() {
String template = "$IllegalId$";
try {
UrlTemplate.compile(template);
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java
index 94dae35..fc99e20 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java
@@ -80,7 +80,7 @@
}
@Test
- public void testCreateWithDefaultDownloaderFactory() {
+ public void createWithDefaultDownloaderFactory() {
DownloaderConstructorHelper constructorHelper =
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
@@ -98,7 +98,7 @@
}
@Test
- public void testDownloadRepresentation() throws Exception {
+ public void downloadRepresentation() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -113,7 +113,7 @@
}
@Test
- public void testDownloadRepresentationInSmallParts() throws Exception {
+ public void downloadRepresentationInSmallParts() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -132,7 +132,7 @@
}
@Test
- public void testDownloadRepresentations() throws Exception {
+ public void downloadRepresentations() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -151,7 +151,7 @@
}
@Test
- public void testDownloadAllRepresentations() throws Exception {
+ public void downloadAllRepresentations() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -172,7 +172,7 @@
}
@Test
- public void testProgressiveDownload() throws Exception {
+ public void progressiveDownload() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -204,7 +204,7 @@
}
@Test
- public void testProgressiveDownloadSeparatePeriods() throws Exception {
+ public void progressiveDownloadSeparatePeriods() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -236,7 +236,7 @@
}
@Test
- public void testDownloadRepresentationFailure() throws Exception {
+ public void downloadRepresentationFailure() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -261,7 +261,7 @@
}
@Test
- public void testCounters() throws Exception {
+ public void counters() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -289,7 +289,7 @@
}
@Test
- public void testRemove() throws Exception {
+ public void remove() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
@@ -309,7 +309,7 @@
}
@Test
- public void testRepresentationWithoutIndex() throws Exception {
+ public void representationWithoutIndex() throws Exception {
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD_NO_INDEX)
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadHelperTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadHelperTest.java
index 107bf7c..5ecdba1 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadHelperTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadHelperTest.java
@@ -35,11 +35,11 @@
ApplicationProvider.getApplicationContext(),
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0]);
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0]);
DownloadHelper.forDash(
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0],
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0],
/* drmSessionManager= */ DrmSessionManager.getDummyDrmSessionManager(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java
index 264b5d3..89426f7 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java
@@ -111,7 +111,7 @@
// Disabled due to flakiness.
@Ignore
@Test
- public void testSaveAndLoadActionFile() throws Throwable {
+ public void saveAndLoadActionFile() throws Throwable {
// Configure fakeDataSet to block until interrupted when TEST_MPD is read.
fakeDataSet
.newData(TEST_MPD_URI)
@@ -152,14 +152,14 @@
}
@Test
- public void testHandleDownloadRequest() throws Throwable {
+ public void handleDownloadRequest_downloadSuccess() throws Throwable {
handleDownloadRequest(fakeStreamKey1, fakeStreamKey2);
blockUntilTasksCompleteAndThrowAnyDownloadError();
assertCachedData(cache, new RequestSet(fakeDataSet).useBoundedDataSpecFor("audio_init_data"));
}
@Test
- public void testHandleMultipleDownloadRequest() throws Throwable {
+ public void handleDownloadRequest_withRequest_downloadSuccess() throws Throwable {
handleDownloadRequest(fakeStreamKey1);
handleDownloadRequest(fakeStreamKey2);
blockUntilTasksCompleteAndThrowAnyDownloadError();
@@ -167,7 +167,7 @@
}
@Test
- public void testHandleInterferingDownloadRequest() throws Throwable {
+ public void handleDownloadRequest_withInferringRequest_success() throws Throwable {
fakeDataSet
.newData("audio_segment_2")
.appendReadAction(() -> handleDownloadRequest(fakeStreamKey2))
@@ -181,7 +181,7 @@
}
@Test
- public void testHandleRemoveAction() throws Throwable {
+ public void handleRemoveAction_blockUntilTaskCompleted_noDownloadedData() throws Throwable {
handleDownloadRequest(fakeStreamKey1);
blockUntilTasksCompleteAndThrowAnyDownloadError();
@@ -194,7 +194,7 @@
}
@Test
- public void testHandleRemoveActionBeforeDownloadFinish() throws Throwable {
+ public void handleRemoveAction_beforeDownloadFinish_noDownloadedData() throws Throwable {
handleDownloadRequest(fakeStreamKey1);
handleRemoveAction();
@@ -204,7 +204,7 @@
}
@Test
- public void testHandleInterferingRemoveAction() throws Throwable {
+ public void handleRemoveAction_withInterfering_noDownloadedData() throws Throwable {
CountDownLatch downloadInProgressLatch = new CountDownLatch(1);
fakeDataSet
.newData("audio_segment_2")
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java
index fd295ea..0bf5089 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java
@@ -130,8 +130,8 @@
return dashDownloadManager;
}
- @Nullable
@Override
+ @Nullable
protected Scheduler getScheduler() {
return null;
}
@@ -154,7 +154,7 @@
@Ignore // b/78877092
@Test
- public void testMultipleDownloadRequest() throws Throwable {
+ public void multipleDownloadRequest() throws Throwable {
downloadKeys(fakeStreamKey1);
downloadKeys(fakeStreamKey2);
@@ -165,7 +165,7 @@
@Ignore // b/78877092
@Test
- public void testRemoveAction() throws Throwable {
+ public void removeAction() throws Throwable {
downloadKeys(fakeStreamKey1, fakeStreamKey2);
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
@@ -179,7 +179,7 @@
@Ignore // b/78877092
@Test
- public void testRemoveBeforeDownloadComplete() throws Throwable {
+ public void removeBeforeDownloadComplete() throws Throwable {
pauseDownloadCondition = new ConditionVariable();
downloadKeys(fakeStreamKey1, fakeStreamKey2);
diff --git a/tree/library/extractor/build.gradle b/tree/library/extractor/build.gradle
index 6d402d3..26b3870 100644
--- a/tree/library/extractor/build.gradle
+++ b/tree/library/extractor/build.gradle
@@ -33,6 +33,8 @@
}
}
+ sourceSets.test.assets.srcDir '../../testdata/src/test/assets/'
+
testOptions.unitTests.includeAndroidResources = true
}
@@ -40,9 +42,11 @@
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation project(modulePrefix + 'library-common')
testImplementation project(modulePrefix + 'library-core')
testImplementation project(modulePrefix + 'testutils')
+ testImplementation project(modulePrefix + 'testdata')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java
index c4375e4..b5eb092 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java
@@ -49,10 +49,9 @@
* @param targetTimestamp The target timestamp.
* @return A {@link TimestampSearchResult} that describes the result of the search.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
- throws IOException, InterruptedException;
+ throws IOException;
/** Called when a seek operation finishes. */
default void onSeekFinished() {}
@@ -169,10 +168,9 @@
* to hold the position of the required seek.
* @return One of the {@code RESULT_} values defined in {@link Extractor}.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
public int handlePendingSeek(ExtractorInput input, PositionHolder seekPositionHolder)
- throws InterruptedException, IOException {
+ throws IOException {
while (true) {
SeekOperationParams seekOperationParams =
Assertions.checkStateNotNull(this.seekOperationParams);
@@ -203,9 +201,9 @@
timestampSearchResult.timestampToUpdate, timestampSearchResult.bytePositionToUpdate);
break;
case TimestampSearchResult.TYPE_TARGET_TIMESTAMP_FOUND:
+ skipInputUntilPosition(input, timestampSearchResult.bytePositionToUpdate);
markSeekOperationFinished(
/* foundTargetFrame= */ true, timestampSearchResult.bytePositionToUpdate);
- skipInputUntilPosition(input, timestampSearchResult.bytePositionToUpdate);
return seekToPosition(
input, timestampSearchResult.bytePositionToUpdate, seekPositionHolder);
case TimestampSearchResult.TYPE_NO_TIMESTAMP:
@@ -241,7 +239,7 @@
}
protected final boolean skipInputUntilPosition(ExtractorInput input, long position)
- throws IOException, InterruptedException {
+ throws IOException {
long bytesToSkip = position - input.getPosition();
if (bytesToSkip >= 0 && bytesToSkip <= MAX_SKIP_BYTES) {
input.skipFully((int) bytesToSkip);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java
index c6f1129..4ab306a 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java
@@ -16,16 +16,15 @@
package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.util.Arrays;
-/**
- * An {@link ExtractorInput} that wraps a {@link DataSource}.
- */
+/** An {@link ExtractorInput} that wraps a {@link DataReader}. */
public final class DefaultExtractorInput implements ExtractorInput {
private static final int PEEK_MIN_FREE_SPACE_AFTER_RESIZE = 64 * 1024;
@@ -33,7 +32,7 @@
private static final int SCRATCH_SPACE_SIZE = 4096;
private final byte[] scratchSpace;
- private final DataSource dataSource;
+ private final DataReader dataReader;
private final long streamLength;
private long position;
@@ -42,12 +41,12 @@
private int peekBufferLength;
/**
- * @param dataSource The wrapped {@link DataSource}.
+ * @param dataReader The wrapped {@link DataReader}.
* @param position The initial position in the stream.
* @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown.
*/
- public DefaultExtractorInput(DataSource dataSource, long position, long length) {
- this.dataSource = dataSource;
+ public DefaultExtractorInput(DataReader dataReader, long position, long length) {
+ this.dataReader = dataReader;
this.position = position;
this.streamLength = length;
peekBuffer = new byte[PEEK_MIN_FREE_SPACE_AFTER_RESIZE];
@@ -55,11 +54,11 @@
}
@Override
- public int read(byte[] target, int offset, int length) throws IOException, InterruptedException {
+ public int read(byte[] target, int offset, int length) throws IOException {
int bytesRead = readFromPeekBuffer(target, offset, length);
if (bytesRead == 0) {
bytesRead =
- readFromDataSource(
+ readFromUpstream(
target, offset, length, /* bytesAlreadyRead= */ 0, /* allowEndOfInput= */ true);
}
commitBytesRead(bytesRead);
@@ -68,58 +67,56 @@
@Override
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ throws IOException {
int bytesRead = readFromPeekBuffer(target, offset, length);
while (bytesRead < length && bytesRead != C.RESULT_END_OF_INPUT) {
- bytesRead = readFromDataSource(target, offset, length, bytesRead, allowEndOfInput);
+ bytesRead = readFromUpstream(target, offset, length, bytesRead, allowEndOfInput);
}
commitBytesRead(bytesRead);
return bytesRead != C.RESULT_END_OF_INPUT;
}
@Override
- public void readFully(byte[] target, int offset, int length)
- throws IOException, InterruptedException {
+ public void readFully(byte[] target, int offset, int length) throws IOException {
readFully(target, offset, length, false);
}
@Override
- public int skip(int length) throws IOException, InterruptedException {
+ public int skip(int length) throws IOException {
int bytesSkipped = skipFromPeekBuffer(length);
if (bytesSkipped == 0) {
bytesSkipped =
- readFromDataSource(scratchSpace, 0, Math.min(length, scratchSpace.length), 0, true);
+ readFromUpstream(scratchSpace, 0, Math.min(length, scratchSpace.length), 0, true);
}
commitBytesRead(bytesSkipped);
return bytesSkipped;
}
@Override
- public boolean skipFully(int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public boolean skipFully(int length, boolean allowEndOfInput) throws IOException {
int bytesSkipped = skipFromPeekBuffer(length);
while (bytesSkipped < length && bytesSkipped != C.RESULT_END_OF_INPUT) {
int minLength = Math.min(length, bytesSkipped + scratchSpace.length);
bytesSkipped =
- readFromDataSource(scratchSpace, -bytesSkipped, minLength, bytesSkipped, allowEndOfInput);
+ readFromUpstream(scratchSpace, -bytesSkipped, minLength, bytesSkipped, allowEndOfInput);
}
commitBytesRead(bytesSkipped);
return bytesSkipped != C.RESULT_END_OF_INPUT;
}
@Override
- public void skipFully(int length) throws IOException, InterruptedException {
+ public void skipFully(int length) throws IOException {
skipFully(length, false);
}
@Override
- public int peek(byte[] target, int offset, int length) throws IOException, InterruptedException {
+ public int peek(byte[] target, int offset, int length) throws IOException {
ensureSpaceForPeek(length);
int peekBufferRemainingBytes = peekBufferLength - peekBufferPosition;
int bytesPeeked;
if (peekBufferRemainingBytes == 0) {
bytesPeeked =
- readFromDataSource(
+ readFromUpstream(
peekBuffer,
peekBufferPosition,
length,
@@ -139,7 +136,7 @@
@Override
public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ throws IOException {
if (!advancePeekPosition(length, allowEndOfInput)) {
return false;
}
@@ -148,19 +145,17 @@
}
@Override
- public void peekFully(byte[] target, int offset, int length)
- throws IOException, InterruptedException {
+ public void peekFully(byte[] target, int offset, int length) throws IOException {
peekFully(target, offset, length, false);
}
@Override
- public boolean advancePeekPosition(int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException {
ensureSpaceForPeek(length);
int bytesPeeked = peekBufferLength - peekBufferPosition;
while (bytesPeeked < length) {
- bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
- allowEndOfInput);
+ bytesPeeked =
+ readFromUpstream(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput);
if (bytesPeeked == C.RESULT_END_OF_INPUT) {
return false;
}
@@ -171,7 +166,7 @@
}
@Override
- public void advancePeekPosition(int length) throws IOException, InterruptedException {
+ public void advancePeekPosition(int length) throws IOException {
advancePeekPosition(length, false);
}
@@ -262,7 +257,7 @@
}
/**
- * Starts or continues a read from the data source.
+ * Starts or continues a read from the data reader.
*
* @param target A target array into which data should be written.
* @param offset The offset into the target array at which to write.
@@ -271,20 +266,20 @@
* @param allowEndOfInput True if encountering the end of the input having read no data is
* allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
* should be considered an error, causing an {@link EOFException} to be thrown.
- * @return The total number of bytes read so far, or {@link C#RESULT_END_OF_INPUT} if
- * {@code allowEndOfInput} is true and the input has ended having read no bytes.
+ * @return The total number of bytes read so far, or {@link C#RESULT_END_OF_INPUT} if {@code
+ * allowEndOfInput} is true and the input has ended having read no bytes.
* @throws EOFException If the end of input was encountered having partially satisfied the read
* (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were
* read and {@code allowEndOfInput} is false.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private int readFromDataSource(byte[] target, int offset, int length, int bytesAlreadyRead,
- boolean allowEndOfInput) throws InterruptedException, IOException {
+ private int readFromUpstream(
+ byte[] target, int offset, int length, int bytesAlreadyRead, boolean allowEndOfInput)
+ throws IOException {
if (Thread.interrupted()) {
- throw new InterruptedException();
+ throw new InterruptedIOException();
}
- int bytesRead = dataSource.read(target, offset + bytesAlreadyRead, length - bytesAlreadyRead);
+ int bytesRead = dataReader.read(target, offset + bytesAlreadyRead, length - bytesAlreadyRead);
if (bytesRead == C.RESULT_END_OF_INPUT) {
if (bytesAlreadyRead == 0 && allowEndOfInput) {
return C.RESULT_END_OF_INPUT;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java
index cdbd374..9306a14 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java
@@ -52,7 +52,13 @@
* <li>AC3 ({@link Ac3Extractor})
* <li>AC4 ({@link Ac4Extractor})
* <li>AMR ({@link AmrExtractor})
- * <li>FLAC (only available if the FLAC extension is built and included)
+ * <li>FLAC
+ * <ul>
+ * <li>If available, the FLAC extension extractor is used.
+ * <li>Otherwise, the core {@link FlacExtractor} is used. Note that Android devices do not
+ * generally include a FLAC decoder before API 27. This can be worked around by using
+ * the FLAC extension or the FFmpeg extension.
+ * </ul>
* </ul>
*/
public final class DefaultExtractorsFactory implements ExtractorsFactory {
@@ -87,14 +93,15 @@
}
private boolean constantBitrateSeekingEnabled;
- private @AdtsExtractor.Flags int adtsFlags;
- private @AmrExtractor.Flags int amrFlags;
- private @MatroskaExtractor.Flags int matroskaFlags;
- private @Mp4Extractor.Flags int mp4Flags;
- private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
- private @Mp3Extractor.Flags int mp3Flags;
- private @TsExtractor.Mode int tsMode;
- private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
+ @AdtsExtractor.Flags private int adtsFlags;
+ @AmrExtractor.Flags private int amrFlags;
+ @FlacExtractor.Flags private int coreFlacFlags;
+ @MatroskaExtractor.Flags private int matroskaFlags;
+ @Mp4Extractor.Flags private int mp4Flags;
+ @FragmentedMp4Extractor.Flags private int fragmentedMp4Flags;
+ @Mp3Extractor.Flags private int mp3Flags;
+ @TsExtractor.Mode private int tsMode;
+ @DefaultTsPayloadReaderFactory.Flags private int tsFlags;
public DefaultExtractorsFactory() {
tsMode = TsExtractor.MODE_SINGLE_PMT;
@@ -143,6 +150,19 @@
}
/**
+ * Sets flags for {@link FlacExtractor} instances created by the factory.
+ *
+ * @see FlacExtractor#FlacExtractor(int)
+ * @param flags The flags to use.
+ * @return The factory, for convenience.
+ */
+ public synchronized DefaultExtractorsFactory setCoreFlacExtractorFlags(
+ @FlacExtractor.Flags int flags) {
+ this.coreFlacFlags = flags;
+ return this;
+ }
+
+ /**
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
*
* @see MatroskaExtractor#MatroskaExtractor(int)
@@ -221,48 +241,46 @@
@Override
public synchronized Extractor[] createExtractors() {
Extractor[] extractors = new Extractor[14];
- extractors[0] = new MatroskaExtractor(matroskaFlags);
- extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
- extractors[2] = new Mp4Extractor(mp4Flags);
- extractors[3] =
- new Mp3Extractor(
- mp3Flags
- | (constantBitrateSeekingEnabled
- ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0));
- extractors[4] =
- new AdtsExtractor(
- adtsFlags
- | (constantBitrateSeekingEnabled
- ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0));
- extractors[5] = new Ac3Extractor();
- extractors[6] = new TsExtractor(tsMode, tsFlags);
- extractors[7] = new FlvExtractor();
- extractors[8] = new OggExtractor();
- extractors[9] = new PsExtractor();
- extractors[10] = new WavExtractor();
- extractors[11] =
- new AmrExtractor(
- amrFlags
- | (constantBitrateSeekingEnabled
- ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0));
- extractors[12] = new Ac4Extractor();
- // Prefer the FLAC extension extractor because it outputs raw audio, which can be handled by the
- // framework on all API levels, unlike the core library FLAC extractor, which outputs FLAC audio
- // frames and so relies on having a FLAC decoder (e.g., a MediaCodec decoder that handles FLAC
- // (from API 27), or the FFmpeg extension with FLAC enabled).
+ // Extractors order is optimized according to
+ // https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
+ extractors[0] = new FlvExtractor();
if (FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR != null) {
try {
- extractors[13] = FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance();
+ extractors[1] = FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance();
} catch (Exception e) {
// Should never happen.
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
}
} else {
- extractors[13] = new FlacExtractor();
+ extractors[1] = new FlacExtractor(coreFlacFlags);
}
+ extractors[2] = new WavExtractor();
+ extractors[3] = new FragmentedMp4Extractor(fragmentedMp4Flags);
+ extractors[4] = new Mp4Extractor(mp4Flags);
+ extractors[5] =
+ new AmrExtractor(
+ amrFlags
+ | (constantBitrateSeekingEnabled
+ ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
+ : 0));
+ extractors[6] = new PsExtractor();
+ extractors[7] = new OggExtractor();
+ extractors[8] = new TsExtractor(tsMode, tsFlags);
+ extractors[9] = new MatroskaExtractor(matroskaFlags);
+ extractors[10] =
+ new AdtsExtractor(
+ adtsFlags
+ | (constantBitrateSeekingEnabled
+ ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
+ : 0));
+ extractors[11] = new Ac3Extractor();
+ extractors[12] = new Ac4Extractor();
+ extractors[13] =
+ new Mp3Extractor(
+ mp3Flags
+ | (constantBitrateSeekingEnabled
+ ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
+ : 0));
return extractors;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java
index f1aecca..7ef308e 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java
@@ -18,6 +18,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.EOFException;
import java.io.IOException;
@@ -27,15 +28,24 @@
*/
public final class DummyTrackOutput implements TrackOutput {
+ // Even though read data is discarded, data source implementations could be making use of the
+ // buffer contents. For example, caches. So we cannot use a static field for this which could be
+ // shared between different threads.
+ private final byte[] readBuffer;
+
+ public DummyTrackOutput() {
+ readBuffer = new byte[4096];
+ }
+
@Override
public void format(Format format) {
// Do nothing.
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
- int bytesSkipped = input.skip(length);
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException {
+ int bytesToSkipByReading = Math.min(readBuffer.length, length);
+ int bytesSkipped = input.read(readBuffer, /* offset= */ 0, bytesToSkipByReading);
if (bytesSkipped == C.RESULT_END_OF_INPUT) {
if (allowEndOfInput) {
return C.RESULT_END_OF_INPUT;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
index a9151a1..d1371d5 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java
@@ -57,16 +57,15 @@
/**
* Returns whether this extractor can extract samples from the {@link ExtractorInput}, which must
* provide data from the start of the stream.
- * <p>
- * If {@code true} is returned, the {@code input}'s reading position may have been modified.
+ *
+ * <p>If {@code true} is returned, the {@code input}'s reading position may have been modified.
* Otherwise, only its peek position may have been modified.
*
* @param input The {@link ExtractorInput} from which data should be peeked/read.
* @return Whether this extractor can read the provided input.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
- boolean sniff(ExtractorInput input) throws IOException, InterruptedException;
+ boolean sniff(ExtractorInput input) throws IOException;
/**
* Initializes the extractor with an {@link ExtractorOutput}. Called at most once.
@@ -89,20 +88,18 @@
* {@link #RESULT_SEEK} is returned. If the extractor reached the end of the data provided by the
* {@link ExtractorInput}, then {@link #RESULT_END_OF_INPUT} is returned.
*
- * <p>When this method throws an {@link IOException} or an {@link InterruptedException},
- * extraction may continue by providing an {@link ExtractorInput} with an unchanged {@link
- * ExtractorInput#getPosition() read position} to a subsequent call to this method.
+ * <p>When this method throws an {@link IOException}, extraction may continue by providing an
+ * {@link ExtractorInput} with an unchanged {@link ExtractorInput#getPosition() read position} to
+ * a subsequent call to this method.
*
* @param input The {@link ExtractorInput} from which data should be read.
* @param seekPosition If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_} values defined in this interface.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
@ReadResult
- int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException;
+ int read(ExtractorInput input, PositionHolder seekPosition) throws IOException;
/**
* Notifies the extractor that a seek has occurred.
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java
index 8e5d6f0..6799ca6 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.upstream.DataReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@@ -63,12 +64,12 @@
* (regardless of {@code allowEndOfInput}).
* </ul>
*/
-public interface ExtractorInput {
+public interface ExtractorInput extends DataReader {
/**
* Reads up to {@code length} bytes from the input and resets the peek position.
- * <p>
- * This method blocks until at least one byte of data can be read, the end of the input is
+ *
+ * <p>This method blocks until at least one byte of data can be read, the end of the input is
* detected, or an exception is thrown.
*
* @param target A target array into which data should be written.
@@ -76,9 +77,9 @@
* @param length The maximum number of bytes to read from the input.
* @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the input has ended.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- int read(byte[] target, int offset, int length) throws IOException, InterruptedException;
+ @Override
+ int read(byte[] target, int offset, int length) throws IOException;
/**
* Like {@link #read(byte[], int, int)}, but reads the requested {@code length} in full.
@@ -96,10 +97,9 @@
* (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were
* read and {@code allowEndOfInput} is false.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException;
+ throws IOException;
/**
* Equivalent to {@link #readFully(byte[], int, int, boolean) readFully(target, offset, length,
@@ -110,9 +110,8 @@
* @param length The number of bytes to read from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- void readFully(byte[] target, int offset, int length) throws IOException, InterruptedException;
+ void readFully(byte[] target, int offset, int length) throws IOException;
/**
* Like {@link #read(byte[], int, int)}, except the data is skipped instead of read.
@@ -120,9 +119,8 @@
* @param length The maximum number of bytes to skip from the input.
* @return The number of bytes skipped, or {@link C#RESULT_END_OF_INPUT} if the input has ended.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- int skip(int length) throws IOException, InterruptedException;
+ int skip(int length) throws IOException;
/**
* Like {@link #readFully(byte[], int, int, boolean)}, except the data is skipped instead of read.
@@ -138,22 +136,20 @@
* (i.e. having skipped at least one byte, but fewer than {@code length}), or if no bytes were
* skipped and {@code allowEndOfInput} is false.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- boolean skipFully(int length, boolean allowEndOfInput) throws IOException, InterruptedException;
+ boolean skipFully(int length, boolean allowEndOfInput) throws IOException;
/**
* Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read.
- * <p>
- * Encountering the end of input is always considered an error, and will result in an
- * {@link EOFException} being thrown.
+ *
+ * <p>Encountering the end of input is always considered an error, and will result in an {@link
+ * EOFException} being thrown.
*
* @param length The number of bytes to skip from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- void skipFully(int length) throws IOException, InterruptedException;
+ void skipFully(int length) throws IOException;
/**
* Peeks up to {@code length} bytes from the peek position. The current read position is left
@@ -171,9 +167,8 @@
* @param length The maximum number of bytes to peek from the input.
* @return The number of bytes peeked, or {@link C#RESULT_END_OF_INPUT} if the input has ended.
* @throws IOException If an error occurs peeking from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- int peek(byte[] target, int offset, int length) throws IOException, InterruptedException;
+ int peek(byte[] target, int offset, int length) throws IOException;
/**
* Like {@link #peek(byte[], int, int)}, but peeks the requested {@code length} in full.
@@ -191,10 +186,9 @@
* (i.e. having peeked at least one byte, but fewer than {@code length}), or if no bytes were
* peeked and {@code allowEndOfInput} is false.
* @throws IOException If an error occurs peeking from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException;
+ throws IOException;
/**
* Equivalent to {@link #peekFully(byte[], int, int, boolean) peekFully(target, offset, length,
@@ -205,9 +199,8 @@
* @param length The number of bytes to peek from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs peeking from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException;
+ void peekFully(byte[] target, int offset, int length) throws IOException;
/**
* Advances the peek position by {@code length} bytes. Like {@link #peekFully(byte[], int, int,
@@ -224,10 +217,8 @@
* advanced by at least one byte, but fewer than {@code length}), or if the end of input was
* encountered before advancing and {@code allowEndOfInput} is false.
* @throws IOException If an error occurs advancing the peek position.
- * @throws InterruptedException If the thread is interrupted.
*/
- boolean advancePeekPosition(int length, boolean allowEndOfInput)
- throws IOException, InterruptedException;
+ boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException;
/**
* Advances the peek position by {@code length} bytes. Like {@link #peekFully(byte[], int, int)}
@@ -236,9 +227,8 @@
* @param length The number of bytes to peek from the input.
* @throws EOFException If the end of input was encountered.
* @throws IOException If an error occurs peeking from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- void advancePeekPosition(int length) throws IOException, InterruptedException;
+ void advancePeekPosition(int length) throws IOException;
/**
* Resets the peek position to equal the current read position.
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java
index 3867a0f..5ce274b 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorUtil.java
@@ -33,10 +33,9 @@
* @param length The maximum number of bytes to peek from the input.
* @return The number of bytes peeked.
* @throws IOException If an error occurs peeking from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
public static int peekToLength(ExtractorInput input, byte[] target, int offset, int length)
- throws IOException, InterruptedException {
+ throws IOException {
int totalBytesPeeked = 0;
while (totalBytesPeeked < length) {
int bytesPeeked = input.peek(target, offset + totalBytesPeeked, length - totalBytesPeeked);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java
index 798cde5..264c6d7 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java
@@ -93,7 +93,7 @@
FlacStreamMetadata flacStreamMetadata,
int frameStartMarker,
SampleNumberHolder sampleNumberHolder)
- throws IOException, InterruptedException {
+ throws IOException {
long originalPeekPosition = input.getPeekPosition();
byte[] frameStartBytes = new byte[2];
@@ -132,11 +132,9 @@
* @return The frame first sample number.
* @throws ParserException If an error occurs parsing the sample number.
* @throws IOException If peeking from the input fails.
- * @throws InterruptedException If interrupted while peeking from input.
*/
public static long getFirstSampleNumber(
- ExtractorInput input, FlacStreamMetadata flacStreamMetadata)
- throws IOException, InterruptedException {
+ ExtractorInput input, FlacStreamMetadata flacStreamMetadata) throws IOException {
input.resetPeekPosition();
input.advancePeekPosition(1);
byte[] blockingStrategyByte = new byte[1];
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacMetadataReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacMetadataReader.java
index cad8b58..65e65c4 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacMetadataReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacMetadataReader.java
@@ -60,12 +60,10 @@
* is {@code false}.
* @throws IOException If peeking from the input fails. In this case, there is no guarantee on the
* peek position.
- * @throws InterruptedException If interrupted while peeking from input. In this case, there is no
- * guarantee on the peek position.
*/
@Nullable
public static Metadata peekId3Metadata(ExtractorInput input, boolean parseData)
- throws IOException, InterruptedException {
+ throws IOException {
@Nullable
Id3Decoder.FramePredicate id3FramePredicate = parseData ? null : Id3Decoder.NO_FRAMES_PREDICATE;
@Nullable Metadata id3Metadata = new Id3Peeker().peekId3Data(input, id3FramePredicate);
@@ -79,11 +77,8 @@
* @return Whether the data peeked is the FLAC stream marker.
* @throws IOException If peeking from the input fails. In this case, the peek position is left
* unchanged.
- * @throws InterruptedException If interrupted while peeking from input. In this case, the peek
- * position is left unchanged.
*/
- public static boolean checkAndPeekStreamMarker(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static boolean checkAndPeekStreamMarker(ExtractorInput input) throws IOException {
ParsableByteArray scratch = new ParsableByteArray(FlacConstants.STREAM_MARKER_SIZE);
input.peekFully(scratch.data, 0, FlacConstants.STREAM_MARKER_SIZE);
return scratch.readUnsignedInt() == STREAM_MARKER;
@@ -101,12 +96,10 @@
* is {@code false}.
* @throws IOException If reading from the input fails. In this case, the read position is left
* unchanged and there is no guarantee on the peek position.
- * @throws InterruptedException If interrupted while reading from input. In this case, the read
- * position is left unchanged and there is no guarantee on the peek position.
*/
@Nullable
public static Metadata readId3Metadata(ExtractorInput input, boolean parseData)
- throws IOException, InterruptedException {
+ throws IOException {
input.resetPeekPosition();
long startingPeekPosition = input.getPeekPosition();
@Nullable Metadata id3Metadata = peekId3Metadata(input, parseData);
@@ -123,11 +116,8 @@
* position of {@code input} is advanced by {@link FlacConstants#STREAM_MARKER_SIZE} bytes.
* @throws IOException If reading from the input fails. In this case, the position is left
* unchanged.
- * @throws InterruptedException If interrupted while reading from input. In this case, the
- * position is left unchanged.
*/
- public static void readStreamMarker(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static void readStreamMarker(ExtractorInput input) throws IOException {
ParsableByteArray scratch = new ParsableByteArray(FlacConstants.STREAM_MARKER_SIZE);
input.readFully(scratch.data, 0, FlacConstants.STREAM_MARKER_SIZE);
if (scratch.readUnsignedInt() != STREAM_MARKER) {
@@ -153,13 +143,9 @@
* start of a metadata block and there is no guarantee on the peek position.
* @throws IOException If reading from the input fails. In this case, the read position will be at
* the start of a metadata block and there is no guarantee on the peek position.
- * @throws InterruptedException If interrupted while reading from input. In this case, the read
- * position will be at the start of a metadata block and there is no guarantee on the peek
- * position.
*/
public static boolean readMetadataBlock(
- ExtractorInput input, FlacStreamMetadataHolder metadataHolder)
- throws IOException, InterruptedException {
+ ExtractorInput input, FlacStreamMetadataHolder metadataHolder) throws IOException {
input.resetPeekPosition();
ParsableBitArray scratch = new ParsableBitArray(new byte[4]);
input.peekFully(scratch.data, 0, FlacConstants.METADATA_BLOCK_HEADER_SIZE);
@@ -239,10 +225,8 @@
* @return The frame start marker (which must be the same for all the frames in the stream).
* @throws ParserException If an error occurs parsing the frame start marker.
* @throws IOException If peeking from the input fails.
- * @throws InterruptedException If interrupted while peeking from input.
*/
- public static int getFrameStartMarker(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static int getFrameStartMarker(ExtractorInput input) throws IOException {
input.resetPeekPosition();
ParsableByteArray scratch = new ParsableByteArray(2);
input.peekFully(scratch.data, 0, 2);
@@ -258,8 +242,7 @@
return frameStartMarker;
}
- private static FlacStreamMetadata readStreamInfoBlock(ExtractorInput input)
- throws IOException, InterruptedException {
+ private static FlacStreamMetadata readStreamInfoBlock(ExtractorInput input) throws IOException {
byte[] scratchData = new byte[FlacConstants.STREAM_INFO_BLOCK_SIZE];
input.readFully(scratchData, 0, FlacConstants.STREAM_INFO_BLOCK_SIZE);
return new FlacStreamMetadata(
@@ -267,14 +250,14 @@
}
private static FlacStreamMetadata.SeekTable readSeekTableMetadataBlock(
- ExtractorInput input, int length) throws IOException, InterruptedException {
+ ExtractorInput input, int length) throws IOException {
ParsableByteArray scratch = new ParsableByteArray(length);
input.readFully(scratch.data, 0, length);
return readSeekTableMetadataBlock(scratch);
}
private static List<String> readVorbisCommentMetadataBlock(ExtractorInput input, int length)
- throws IOException, InterruptedException {
+ throws IOException {
ParsableByteArray scratch = new ParsableByteArray(length);
input.readFully(scratch.data, 0, length);
scratch.skipBytes(FlacConstants.METADATA_BLOCK_HEADER_SIZE);
@@ -285,7 +268,7 @@
}
private static PictureFrame readPictureMetadataBlock(ExtractorInput input, int length)
- throws IOException, InterruptedException {
+ throws IOException {
ParsableByteArray scratch = new ParsableByteArray(length);
input.readFully(scratch.data, 0, length);
scratch.skipBytes(FlacConstants.METADATA_BLOCK_HEADER_SIZE);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacStreamMetadata.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacStreamMetadata.java
index 8d16f44..a4a2b64 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacStreamMetadata.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/FlacStreamMetadata.java
@@ -182,8 +182,8 @@
return maxBlockSizeSamples * channels * (bitsPerSample / 8);
}
- /** Returns the bit-rate of the FLAC stream. */
- public int getBitRate() {
+ /** Returns the bitrate of the stream after it's decoded into PCM. */
+ public int getDecodedBitrate() {
return bitsPerSample * sampleRate * channels;
}
@@ -239,23 +239,14 @@
streamMarkerAndInfoBlock[4] = (byte) 0x80;
int maxInputSize = maxFrameSize > 0 ? maxFrameSize : Format.NO_VALUE;
@Nullable Metadata metadataWithId3 = getMetadataCopyWithAppendedEntriesFrom(id3Metadata);
-
- return Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_FLAC,
- /* codecs= */ null,
- getBitRate(),
- maxInputSize,
- channels,
- sampleRate,
- /* pcmEncoding= */ Format.NO_VALUE,
- /* encoderDelay= */ 0,
- /* encoderPadding= */ 0,
- /* initializationData= */ Collections.singletonList(streamMarkerAndInfoBlock),
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null,
- metadataWithId3);
+ return new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_FLAC)
+ .setMaxInputSize(maxInputSize)
+ .setChannelCount(channels)
+ .setSampleRate(sampleRate)
+ .setInitializationData(Collections.singletonList(streamMarkerAndInfoBlock))
+ .setMetadata(metadataWithId3)
+ .build();
}
/** Returns a copy of the content metadata with entries from {@code other} appended. */
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Id3Peeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Id3Peeker.java
index f2eb644..cda6a80 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Id3Peeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/Id3Peeker.java
@@ -43,12 +43,11 @@
* @return The first ID3 tag decoded into a {@link Metadata} object. May be null if ID3 tag is not
* present in the input.
* @throws IOException If an error occurred peeking from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
@Nullable
public Metadata peekId3Data(
ExtractorInput input, @Nullable Id3Decoder.FramePredicate id3FramePredicate)
- throws IOException, InterruptedException {
+ throws IOException {
int peekedId3Bytes = 0;
@Nullable Metadata metadata = null;
while (true) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java
index 0d5a168..3e95fab 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java
@@ -18,6 +18,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.EOFException;
import java.io.IOException;
@@ -103,17 +104,15 @@
/**
* Called to write sample data to the output.
*
- * @param input An {@link ExtractorInput} from which to read the sample data.
+ * @param input A {@link DataReader} from which to read the sample data.
* @param length The maximum length to read from the input.
* @param allowEndOfInput True if encountering the end of the input having read no data is
* allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
* should be considered an error, causing an {@link EOFException} to be thrown.
* @return The number of bytes appended.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
- int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException;
+ int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException;
/**
* Called to write sample data to the output.
@@ -127,15 +126,14 @@
* Called when metadata associated with a sample has been extracted from the stream.
*
* <p>The corresponding sample data will have already been passed to the output via calls to
- * {@link #sampleData(ExtractorInput, int, boolean)} or {@link #sampleData(ParsableByteArray,
- * int)}.
+ * {@link #sampleData(DataReader, int, boolean)} or {@link #sampleData(ParsableByteArray, int)}.
*
* @param timeUs The media timestamp associated with the sample, in microseconds.
* @param flags Flags associated with the sample. See {@code C.BUFFER_FLAG_*}.
* @param size The size of the sample data, in bytes.
- * @param offset The number of bytes that have been passed to {@link #sampleData(ExtractorInput,
- * int, boolean)} or {@link #sampleData(ParsableByteArray, int)} since the last byte belonging
- * to the sample whose metadata is being passed.
+ * @param offset The number of bytes that have been passed to {@link #sampleData(DataReader, int,
+ * boolean)} or {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to
+ * the sample whose metadata is being passed.
* @param encryptionData The encryption data required to decrypt the sample. May be null.
*/
void sampleMetadata(
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/VorbisUtil.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/VorbisUtil.java
index 5066c3a..67d469b 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/VorbisUtil.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/VorbisUtil.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor;
+import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
@@ -37,27 +38,54 @@
}
}
- /** Vorbis identification header. */
+ /**
+ * Vorbis identification header.
+ *
+ * @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-630004.2.2">Vorbis
+ * spec/Identification header</a>
+ */
public static final class VorbisIdHeader {
- public final long version;
+ /** The {@code vorbis_version} field. */
+ public final int version;
+ /** The {@code audio_channels} field. */
public final int channels;
- public final long sampleRate;
- public final int bitrateMax;
+ /** The {@code audio_sample_rate} field. */
+ public final int sampleRate;
+ /** The {@code bitrate_maximum} field, or {@link Format#NO_VALUE} if not greater than zero. */
+ public final int bitrateMaximum;
+ /** The {@code bitrate_nominal} field, or {@link Format#NO_VALUE} if not greater than zero. */
public final int bitrateNominal;
- public final int bitrateMin;
+ /** The {@code bitrate_minimum} field, or {@link Format#NO_VALUE} if not greater than zero. */
+ public final int bitrateMinimum;
+ /** The {@code blocksize_0} field. */
public final int blockSize0;
+ /** The {@code blocksize_1} field. */
public final int blockSize1;
+ /** The {@code framing_flag} field. */
public final boolean framingFlag;
+ /** The raw header data. */
public final byte[] data;
+ /**
+ * @param version See {@link #version}.
+ * @param channels See {@link #channels}.
+ * @param sampleRate See {@link #sampleRate}.
+ * @param bitrateMaximum See {@link #bitrateMaximum}.
+ * @param bitrateNominal See {@link #bitrateNominal}.
+ * @param bitrateMinimum See {@link #bitrateMinimum}.
+ * @param blockSize0 See {@link #version}.
+ * @param blockSize1 See {@link #blockSize1}.
+ * @param framingFlag See {@link #framingFlag}.
+ * @param data See {@link #data}.
+ */
public VorbisIdHeader(
- long version,
+ int version,
int channels,
- long sampleRate,
- int bitrateMax,
+ int sampleRate,
+ int bitrateMaximum,
int bitrateNominal,
- int bitrateMin,
+ int bitrateMinimum,
int blockSize0,
int blockSize1,
boolean framingFlag,
@@ -65,18 +93,14 @@
this.version = version;
this.channels = channels;
this.sampleRate = sampleRate;
- this.bitrateMax = bitrateMax;
+ this.bitrateMaximum = bitrateMaximum;
this.bitrateNominal = bitrateNominal;
- this.bitrateMin = bitrateMin;
+ this.bitrateMinimum = bitrateMinimum;
this.blockSize0 = blockSize0;
this.blockSize1 = blockSize1;
this.framingFlag = framingFlag;
this.data = data;
}
-
- public int getApproximateBitrate() {
- return bitrateNominal == 0 ? (bitrateMin + bitrateMax) / 2 : bitrateNominal;
- }
}
/** Vorbis setup header modes. */
@@ -128,13 +152,21 @@
verifyVorbisHeaderCapturePattern(0x01, headerData, false);
- long version = headerData.readLittleEndianUnsignedInt();
+ int version = headerData.readLittleEndianUnsignedIntToInt();
int channels = headerData.readUnsignedByte();
- long sampleRate = headerData.readLittleEndianUnsignedInt();
- int bitrateMax = headerData.readLittleEndianInt();
+ int sampleRate = headerData.readLittleEndianUnsignedIntToInt();
+ int bitrateMaximum = headerData.readLittleEndianInt();
+ if (bitrateMaximum <= 0) {
+ bitrateMaximum = Format.NO_VALUE;
+ }
int bitrateNominal = headerData.readLittleEndianInt();
- int bitrateMin = headerData.readLittleEndianInt();
-
+ if (bitrateNominal <= 0) {
+ bitrateNominal = Format.NO_VALUE;
+ }
+ int bitrateMinimum = headerData.readLittleEndianInt();
+ if (bitrateMinimum <= 0) {
+ bitrateMinimum = Format.NO_VALUE;
+ }
int blockSize = headerData.readUnsignedByte();
int blockSize0 = (int) Math.pow(2, blockSize & 0x0F);
int blockSize1 = (int) Math.pow(2, (blockSize & 0xF0) >> 4);
@@ -143,8 +175,17 @@
// raw data of Vorbis setup header has to be passed to decoder as CSD buffer #1
byte[] data = Arrays.copyOf(headerData.data, headerData.limit());
- return new VorbisIdHeader(version, channels, sampleRate, bitrateMax, bitrateNominal, bitrateMin,
- blockSize0, blockSize1, framingFlag, data);
+ return new VorbisIdHeader(
+ version,
+ channels,
+ sampleRate,
+ bitrateMaximum,
+ bitrateNominal,
+ bitrateMinimum,
+ blockSize0,
+ blockSize1,
+ framingFlag,
+ data);
}
/**
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java
index f1acac6..4d8ef18 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java
@@ -160,7 +160,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
return readAmrHeader(input);
}
@@ -172,8 +172,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
assertInitialized();
if (input.getPosition() == 0) {
if (!readAmrHeader(input)) {
@@ -227,7 +226,7 @@
* @param input The {@link ExtractorInput} from which data should be peeked/read.
* @return Whether the AMR header has been read.
*/
- private boolean readAmrHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readAmrHeader(ExtractorInput input) throws IOException {
if (peekAmrSignature(input, amrSignatureNb)) {
isWideBand = false;
input.skipFully(amrSignatureNb.length);
@@ -241,8 +240,8 @@
}
/** Peeks from the beginning of the input to see if the given AMR signature exists. */
- private boolean peekAmrSignature(ExtractorInput input, byte[] amrSignature)
- throws IOException, InterruptedException {
+ private static boolean peekAmrSignature(ExtractorInput input, byte[] amrSignature)
+ throws IOException {
input.resetPeekPosition();
byte[] header = new byte[amrSignature.length];
input.peekFully(header, 0, amrSignature.length);
@@ -256,24 +255,17 @@
String mimeType = isWideBand ? MimeTypes.AUDIO_AMR_WB : MimeTypes.AUDIO_AMR_NB;
int sampleRate = isWideBand ? SAMPLE_RATE_WB : SAMPLE_RATE_NB;
trackOutput.format(
- Format.createAudioSampleFormat(
- /* id= */ null,
- mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- MAX_FRAME_SIZE_BYTES,
- /* channelCount= */ 1,
- sampleRate,
- /* pcmEncoding= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null));
+ new Format.Builder()
+ .setSampleMimeType(mimeType)
+ .setMaxInputSize(MAX_FRAME_SIZE_BYTES)
+ .setChannelCount(1)
+ .setSampleRate(sampleRate)
+ .build());
}
}
@RequiresNonNull("trackOutput")
- private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
+ private int readSample(ExtractorInput extractorInput) throws IOException {
if (currentSampleBytesRemaining == 0) {
try {
currentSampleSize = peekNextSampleSize(extractorInput);
@@ -311,8 +303,7 @@
return RESULT_CONTINUE;
}
- private int peekNextSampleSize(ExtractorInput extractorInput)
- throws IOException, InterruptedException {
+ private int peekNextSampleSize(ExtractorInput extractorInput) throws IOException {
extractorInput.resetPeekPosition();
extractorInput.peekFully(scratch, /* offset= */ 0, /* length= */ 1);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacBinarySearchSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacBinarySearchSeeker.java
index 2e13bf8..03fd1e7 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacBinarySearchSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacBinarySearchSeeker.java
@@ -73,7 +73,7 @@
@Override
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetSampleNumber)
- throws IOException, InterruptedException {
+ throws IOException {
long searchPosition = input.getPosition();
// Find left frame.
@@ -110,10 +110,8 @@
* the stream if no frame was found.
* @throws IOException If peeking from the input fails. In this case, there is no guarantee on
* the peek position.
- * @throws InterruptedException If interrupted while peeking from input. In this case, there is
- * no guarantee on the peek position.
*/
- private long findNextFrame(ExtractorInput input) throws IOException, InterruptedException {
+ private long findNextFrame(ExtractorInput input) throws IOException {
while (input.getPeekPosition() < input.getLength() - FlacConstants.MIN_FRAME_HEADER_SIZE
&& !FlacFrameReader.checkFrameHeaderFromPeek(
input, flacStreamMetadata, frameStartMarker, sampleNumberHolder)) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java
index b93c4ba..f0da265 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flac/FlacExtractor.java
@@ -134,7 +134,7 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false);
return FlacMetadataReader.checkAndPeekStreamMarker(input);
}
@@ -148,7 +148,7 @@
@Override
public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ throws IOException {
switch (state) {
case STATE_READ_ID3_METADATA:
readId3Metadata(input);
@@ -191,24 +191,23 @@
// Private methods.
- private void readId3Metadata(ExtractorInput input) throws IOException, InterruptedException {
+ private void readId3Metadata(ExtractorInput input) throws IOException {
id3Metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ !id3MetadataDisabled);
state = STATE_GET_STREAM_MARKER_AND_INFO_BLOCK_BYTES;
}
- private void getStreamMarkerAndInfoBlockBytes(ExtractorInput input)
- throws IOException, InterruptedException {
+ private void getStreamMarkerAndInfoBlockBytes(ExtractorInput input) throws IOException {
input.peekFully(streamMarkerAndInfoBlock, 0, streamMarkerAndInfoBlock.length);
input.resetPeekPosition();
state = STATE_READ_STREAM_MARKER;
}
- private void readStreamMarker(ExtractorInput input) throws IOException, InterruptedException {
+ private void readStreamMarker(ExtractorInput input) throws IOException {
FlacMetadataReader.readStreamMarker(input);
state = STATE_READ_METADATA_BLOCKS;
}
- private void readMetadataBlocks(ExtractorInput input) throws IOException, InterruptedException {
+ private void readMetadataBlocks(ExtractorInput input) throws IOException {
boolean isLastMetadataBlock = false;
FlacMetadataReader.FlacStreamMetadataHolder metadataHolder =
new FlacMetadataReader.FlacStreamMetadataHolder(flacStreamMetadata);
@@ -226,7 +225,7 @@
state = STATE_GET_FRAME_START_MARKER;
}
- private void getFrameStartMarker(ExtractorInput input) throws IOException, InterruptedException {
+ private void getFrameStartMarker(ExtractorInput input) throws IOException {
frameStartMarker = FlacMetadataReader.getFrameStartMarker(input);
castNonNull(extractorOutput)
.seekMap(
@@ -238,7 +237,7 @@
}
private @ReadResult int readFrames(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ throws IOException {
Assertions.checkNotNull(trackOutput);
Assertions.checkNotNull(flacStreamMetadata);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java
index 4a90484..0ca65e4 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java
@@ -15,12 +15,11 @@
*/
package com.google.android.exoplayer2.extractor.flv;
-import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.extractor.TrackOutput;
-import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Collections;
@@ -62,27 +61,23 @@
if (audioFormat == AUDIO_FORMAT_MP3) {
int sampleRateIndex = (header >> 2) & 0x03;
int sampleRate = AUDIO_SAMPLING_RATE_TABLE[sampleRateIndex];
- Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_MPEG, null,
- Format.NO_VALUE, Format.NO_VALUE, 1, sampleRate, null, null, 0, null);
+ Format format =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_MPEG)
+ .setChannelCount(1)
+ .setSampleRate(sampleRate)
+ .build();
output.format(format);
hasOutputFormat = true;
} else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) {
- String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW
- : MimeTypes.AUDIO_MLAW;
+ String mimeType =
+ audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW : MimeTypes.AUDIO_MLAW;
Format format =
- Format.createAudioSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ type,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 1,
- /* sampleRate= */ 8000,
- /* pcmEncoding= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(mimeType)
+ .setChannelCount(1)
+ .setSampleRate(8000)
+ .build();
output.format(format);
hasOutputFormat = true;
} else if (audioFormat != AUDIO_FORMAT_AAC) {
@@ -109,11 +104,15 @@
// Parse the sequence header.
byte[] audioSpecificConfig = new byte[data.bytesLeft()];
data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length);
- Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
- audioSpecificConfig);
- Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
- Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
- Collections.singletonList(audioSpecificConfig), null, 0, null);
+ AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(audioSpecificConfig);
+ Format format =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setCodecs(aacConfig.codecs)
+ .setChannelCount(aacConfig.channelCount)
+ .setSampleRate(aacConfig.sampleRateHz)
+ .setInitializationData(Collections.singletonList(audioSpecificConfig))
+ .build();
output.format(format);
hasOutputFormat = true;
return false;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java
index b55b698..98c5fa7 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java
@@ -96,7 +96,7 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
// Check if file starts with "FLV" tag
input.peekFully(scratch.data, 0, 3);
scratch.setPosition(0);
@@ -144,8 +144,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
- InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(extractorOutput); // Asserts that init has been called.
while (true) {
switch (state) {
@@ -180,10 +179,9 @@
* @param input The {@link ExtractorInput} from which to read.
* @return True if header was read successfully. False if the end of stream was reached.
* @throws IOException If an error occurred reading or parsing data from the source.
- * @throws InterruptedException If the thread was interrupted.
*/
@RequiresNonNull("extractorOutput")
- private boolean readFlvHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readFlvHeader(ExtractorInput input) throws IOException {
if (!input.readFully(headerBuffer.data, 0, FLV_HEADER_SIZE, true)) {
// We've reached the end of the stream.
return false;
@@ -215,9 +213,8 @@
*
* @param input The {@link ExtractorInput} from which to read.
* @throws IOException If an error occurred skipping data from the source.
- * @throws InterruptedException If the thread was interrupted.
*/
- private void skipToTagHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private void skipToTagHeader(ExtractorInput input) throws IOException {
input.skipFully(bytesToNextTagHeader);
bytesToNextTagHeader = 0;
state = STATE_READING_TAG_HEADER;
@@ -229,9 +226,8 @@
* @param input The {@link ExtractorInput} from which to read.
* @return True if tag header was read successfully. Otherwise, false.
* @throws IOException If an error occurred reading or parsing data from the source.
- * @throws InterruptedException If the thread was interrupted.
*/
- private boolean readTagHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readTagHeader(ExtractorInput input) throws IOException {
if (!input.readFully(tagHeaderBuffer.data, 0, FLV_TAG_HEADER_SIZE, true)) {
// We've reached the end of the stream.
return false;
@@ -253,10 +249,9 @@
* @param input The {@link ExtractorInput} from which to read.
* @return True if the data was consumed by a reader. False if it was skipped.
* @throws IOException If an error occurred reading or parsing data from the source.
- * @throws InterruptedException If the thread was interrupted.
*/
@RequiresNonNull("extractorOutput")
- private boolean readTagData(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readTagData(ExtractorInput input) throws IOException {
boolean wasConsumed = true;
boolean wasSampleOutput = false;
long timestampUs = getCurrentTimestampUs();
@@ -287,8 +282,7 @@
return wasConsumed;
}
- private ParsableByteArray prepareTagData(ExtractorInput input) throws IOException,
- InterruptedException {
+ private ParsableByteArray prepareTagData(ExtractorInput input) throws IOException {
if (tagDataSize > tagData.capacity()) {
tagData.reset(new byte[Math.max(tagData.capacity() * 2, tagDataSize)], 0);
} else {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java
index 5ddaafb..891b228 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java
@@ -90,9 +90,14 @@
AvcConfig avcConfig = AvcConfig.parse(videoSequence);
nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength;
// Construct and output the format.
- Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
- Format.NO_VALUE, Format.NO_VALUE, avcConfig.width, avcConfig.height, Format.NO_VALUE,
- avcConfig.initializationData, Format.NO_VALUE, avcConfig.pixelWidthAspectRatio, null);
+ Format format =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setWidth(avcConfig.width)
+ .setHeight(avcConfig.height)
+ .setPixelWidthHeightRatio(avcConfig.pixelWidthAspectRatio)
+ .setInitializationData(avcConfig.initializationData)
+ .build();
output.format(format);
hasOutputFormat = true;
return false;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java
index d52697c..754cd7a 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java
@@ -79,7 +79,7 @@
}
@Override
- public boolean read(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean read(ExtractorInput input) throws IOException {
Assertions.checkStateNotNull(processor);
while (true) {
MasterElement head = masterElementsStack.peek();
@@ -160,11 +160,9 @@
* @throws EOFException If the end of input was encountered when searching for the next level 1
* element.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
@RequiresNonNull("processor")
- private long maybeResyncToNextLevel1Element(ExtractorInput input)
- throws IOException, InterruptedException {
+ private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException {
input.resetPeekPosition();
while (true) {
input.peekFully(scratch, 0, MAX_ID_BYTES);
@@ -187,10 +185,8 @@
* @param byteLength The length of the integer being read.
* @return The read integer value.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private long readInteger(ExtractorInput input, int byteLength)
- throws IOException, InterruptedException {
+ private long readInteger(ExtractorInput input, int byteLength) throws IOException {
input.readFully(scratch, 0, byteLength);
long value = 0;
for (int i = 0; i < byteLength; i++) {
@@ -206,10 +202,8 @@
* @param byteLength The length of the float being read.
* @return The read float value.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private double readFloat(ExtractorInput input, int byteLength)
- throws IOException, InterruptedException {
+ private double readFloat(ExtractorInput input, int byteLength) throws IOException {
long integerValue = readInteger(input, byteLength);
double floatValue;
if (byteLength == VALID_FLOAT32_ELEMENT_SIZE_BYTES) {
@@ -228,10 +222,8 @@
* @param byteLength The length of the string being read, including zero padding.
* @return The read string value.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private String readString(ExtractorInput input, int byteLength)
- throws IOException, InterruptedException {
+ private static String readString(ExtractorInput input, int byteLength) throws IOException {
if (byteLength == 0) {
return "";
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlProcessor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlProcessor.java
index 01fe5ff..7291ae9 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlProcessor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlProcessor.java
@@ -130,21 +130,18 @@
/**
* Called when a binary element is encountered.
- * <p>
- * The element header (containing the element ID and content size) will already have been read.
- * Implementations are required to consume the whole remainder of the element, which is
- * {@code contentSize} bytes in length, before returning. Implementations are permitted to fail
- * (by throwing an exception) having partially consumed the data, however if they do this, they
- * must consume the remainder of the content when called again.
+ *
+ * <p>The element header (containing the element ID and content size) will already have been read.
+ * Implementations are required to consume the whole remainder of the element, which is {@code
+ * contentSize} bytes in length, before returning. Implementations are permitted to fail (by
+ * throwing an exception) having partially consumed the data, however if they do this, they must
+ * consume the remainder of the content when called again.
*
* @param id The element ID.
* @param contentsSize The element's content size.
* @param input The {@link ExtractorInput} from which data should be read.
* @throws ParserException If a parsing error occurs.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- void binaryElement(int id, int contentsSize, ExtractorInput input)
- throws IOException, InterruptedException;
-
+ void binaryElement(int id, int contentsSize, ExtractorInput input) throws IOException;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java
index c3f00a2..fc32fba 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java
@@ -50,8 +50,6 @@
* @return True if data can continue to be read. False if the end of the input was encountered.
* @throws ParserException If parsing fails.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- boolean read(ExtractorInput input) throws IOException, InterruptedException;
-
+ boolean read(ExtractorInput input) throws IOException;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
index e04dc89..6e66049 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
@@ -420,7 +420,7 @@
}
@Override
- public final boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public final boolean sniff(ExtractorInput input) throws IOException {
return new Sniffer().sniff(input);
}
@@ -448,8 +448,7 @@
}
@Override
- public final int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public final int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
haveOutputSample = false;
boolean continueReading = true;
while (continueReading && !haveOutputSample) {
@@ -1051,8 +1050,7 @@
* @see EbmlProcessor#binaryElement(int, int, ExtractorInput)
*/
@CallSuper
- protected void binaryElement(int id, int contentSize, ExtractorInput input)
- throws IOException, InterruptedException {
+ protected void binaryElement(int id, int contentSize, ExtractorInput input) throws IOException {
switch (id) {
case ID_SEEK_ID:
Arrays.fill(seekEntryIdBytes.data, (byte) 0);
@@ -1231,7 +1229,7 @@
protected void handleBlockAdditionalData(
Track track, int blockAdditionalId, ExtractorInput input, int contentSize)
- throws IOException, InterruptedException {
+ throws IOException {
if (blockAdditionalId == BLOCK_ADDITIONAL_ID_VP9_ITU_T_35
&& CODEC_ID_VP9.equals(track.codecId)) {
blockAdditionalData.reset(contentSize);
@@ -1282,8 +1280,7 @@
* Ensures {@link #scratch} contains at least {@code requiredLength} bytes of data, reading from
* the extractor input if necessary.
*/
- private void readScratch(ExtractorInput input, int requiredLength)
- throws IOException, InterruptedException {
+ private void readScratch(ExtractorInput input, int requiredLength) throws IOException {
if (scratch.limit() >= requiredLength) {
return;
}
@@ -1303,10 +1300,8 @@
* @param size The size of the sample data on the input side.
* @return The final size of the written sample.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private int writeSampleData(ExtractorInput input, Track track, int size)
- throws IOException, InterruptedException {
+ private int writeSampleData(ExtractorInput input, Track track, int size) throws IOException {
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
writeSubtitleSampleData(input, SUBRIP_PREFIX, size);
return finishWriteSampleData();
@@ -1506,7 +1501,7 @@
}
private void writeSubtitleSampleData(ExtractorInput input, byte[] samplePrefix, int size)
- throws IOException, InterruptedException {
+ throws IOException {
int sizeWithPrefix = samplePrefix.length + size;
if (subtitleSample.capacity() < sizeWithPrefix) {
// Initialize subripSample to contain the required prefix and have space to hold a subtitle
@@ -1581,7 +1576,7 @@
* pending {@link #sampleStrippedBytes} and any remaining data read from {@code input}.
*/
private void writeToTarget(ExtractorInput input, byte[] target, int offset, int length)
- throws IOException, InterruptedException {
+ throws IOException {
int pendingStrippedBytes = Math.min(length, sampleStrippedBytes.bytesLeft());
input.readFully(target, offset + pendingStrippedBytes, length - pendingStrippedBytes);
if (pendingStrippedBytes > 0) {
@@ -1594,7 +1589,7 @@
* {@link #sampleStrippedBytes} or data read from {@code input}.
*/
private int writeToOutput(ExtractorInput input, TrackOutput output, int length)
- throws IOException, InterruptedException {
+ throws IOException {
int bytesWritten;
int strippedBytesLeft = sampleStrippedBytes.bytesLeft();
if (strippedBytesLeft > 0) {
@@ -1774,8 +1769,7 @@
}
@Override
- public void binaryElement(int id, int contentsSize, ExtractorInput input)
- throws IOException, InterruptedException {
+ public void binaryElement(int id, int contentsSize, ExtractorInput input) throws IOException {
MatroskaExtractor.this.binaryElement(id, contentsSize, input);
}
}
@@ -1803,7 +1797,7 @@
chunkSampleCount = 0;
}
- public void startSample(ExtractorInput input) throws IOException, InterruptedException {
+ public void startSample(ExtractorInput input) throws IOException {
if (foundSyncframe) {
return;
}
@@ -2064,18 +2058,20 @@
throw new ParserException("Unrecognized codec identifier.");
}
- int type;
- Format format;
@C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
+
+ int type;
+ Format.Builder formatBuilder = new Format.Builder();
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) {
type = C.TRACK_TYPE_AUDIO;
- format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
- Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding,
- initializationData, drmInitData, selectionFlags, language);
+ formatBuilder
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setPcmEncoding(pcmEncoding);
} else if (MimeTypes.isVideo(mimeType)) {
type = C.TRACK_TYPE_VIDEO;
if (displayUnit == Track.DISPLAY_UNIT_PIXELS) {
@@ -2086,9 +2082,9 @@
if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) {
pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight);
}
- ColorInfo colorInfo = null;
+ @Nullable ColorInfo colorInfo = null;
if (hasColorInfo) {
- byte[] hdrStaticInfo = getHdrStaticInfo();
+ @Nullable byte[] hdrStaticInfo = getHdrStaticInfo();
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
}
int rotationDegrees = Format.NO_VALUE;
@@ -2117,53 +2113,40 @@
rotationDegrees = 270;
}
}
- format =
- Format.createVideoSampleFormat(
- Integer.toString(trackId),
- mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- maxInputSize,
- width,
- height,
- /* frameRate= */ Format.NO_VALUE,
- initializationData,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- drmInitData);
+ formatBuilder
+ .setWidth(width)
+ .setHeight(height)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .setRotationDegrees(rotationDegrees)
+ .setProjectionData(projectionData)
+ .setStereoMode(stereoMode)
+ .setColorInfo(colorInfo);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
- format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags,
- language, drmInitData);
} else if (MimeTypes.TEXT_SSA.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
initializationData = new ArrayList<>(2);
initializationData.add(SSA_DIALOGUE_FORMAT);
initializationData.add(codecPrivate);
- format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
- Format.NO_VALUE, selectionFlags, language, Format.NO_VALUE, drmInitData,
- Format.OFFSET_SAMPLE_RELATIVE, initializationData);
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType)
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
- format =
- Format.createImageSampleFormat(
- Integer.toString(trackId),
- mimeType,
- null,
- Format.NO_VALUE,
- selectionFlags,
- initializationData,
- language,
- drmInitData);
} else {
throw new ParserException("Unexpected MIME type.");
}
+ Format format =
+ formatBuilder
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setMaxInputSize(maxInputSize)
+ .setLanguage(language)
+ .setSelectionFlags(selectionFlags)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .build();
+
this.output = output.track(number, type);
this.output.format(format);
}
@@ -2328,7 +2311,5 @@
throw new ParserException("Error parsing MS/ACM codec private");
}
}
-
}
-
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java
index 62c9404..d380fa4 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java
@@ -39,10 +39,8 @@
scratch = new ParsableByteArray(8);
}
- /**
- * @see com.google.android.exoplayer2.extractor.Extractor#sniff(ExtractorInput)
- */
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ /** @see com.google.android.exoplayer2.extractor.Extractor#sniff(ExtractorInput) */
+ public boolean sniff(ExtractorInput input) throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH
? SEARCH_LENGTH : inputLength);
@@ -86,10 +84,8 @@
return peekLength == headerStart + headerSize;
}
- /**
- * Peeks a variable-length unsigned EBML integer from the input.
- */
- private long readUint(ExtractorInput input) throws IOException, InterruptedException {
+ /** Peeks a variable-length unsigned EBML integer from the input. */
+ private long readUint(ExtractorInput input) throws IOException {
input.peekFully(scratch.data, 0, 1);
int value = scratch.data[0] & 0xFF;
if (value == 0) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java
index a94a5ec..6b244de 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java
@@ -56,15 +56,15 @@
}
/**
- * Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that
- * reading can be resumed later if an error occurs having read only some of it.
- * <p>
- * If an value is successfully read, then the reader will automatically reset itself ready to
+ * Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that reading
+ * can be resumed later if an error occurs having read only some of it.
+ *
+ * <p>If an value is successfully read, then the reader will automatically reset itself ready to
* read another value.
- * <p>
- * If an {@link IOException} or {@link InterruptedException} is throw, the read can be resumed
- * later by calling this method again, passing an {@link ExtractorInput} providing data starting
- * where the previous one left off.
+ *
+ * <p>If an {@link IOException} is thrown, the read can be resumed later by calling this method
+ * again, passing an {@link ExtractorInput} providing data starting where the previous one left
+ * off.
*
* @param input The {@link ExtractorInput} from which the integer should be read.
* @param allowEndOfInput True if encountering the end of the input having read no data is
@@ -76,10 +76,13 @@
* and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the
* length of the varint exceeded maximumAllowedLength.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput,
- boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException {
+ public long readUnsignedVarint(
+ ExtractorInput input,
+ boolean allowEndOfInput,
+ boolean removeLengthMask,
+ int maximumAllowedLength)
+ throws IOException {
if (state == STATE_BEGIN_READING) {
// Read the first byte to establish the length.
if (!input.readFully(scratch, 0, 1, allowEndOfInput)) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java
index 73389a0..f74c3fb 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java
@@ -31,6 +31,9 @@
*/
public ConstantBitrateSeeker(
long inputLength, long firstFramePosition, MpegAudioUtil.Header mpegAudioHeader) {
+ // Set the seeker frame size to the size of the first frame (even though some constant bitrate
+ // streams have variable frame sizes) to avoid the need to re-synchronize for constant frame
+ // size streams.
super(inputLength, firstFramePosition, mpegAudioHeader.bitrate, mpegAudioHeader.frameSize);
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java
new file mode 100644
index 0000000..f8c63ff
--- /dev/null
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.extractor.mp3;
+
+import androidx.annotation.VisibleForTesting;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.SeekPoint;
+import com.google.android.exoplayer2.util.LongArray;
+import com.google.android.exoplayer2.util.Util;
+
+/** MP3 seeker that builds a time-to-byte mapping as the stream is read. */
+/* package */ final class IndexSeeker implements Seeker {
+
+ @VisibleForTesting
+ /* package */ static final long MIN_TIME_BETWEEN_POINTS_US = C.MICROS_PER_SECOND / 10;
+
+ private final long dataEndPosition;
+ private final LongArray timesUs;
+ private final LongArray positions;
+
+ private long durationUs;
+
+ public IndexSeeker(long durationUs, long dataStartPosition, long dataEndPosition) {
+ this.durationUs = durationUs;
+ this.dataEndPosition = dataEndPosition;
+ timesUs = new LongArray();
+ positions = new LongArray();
+ timesUs.add(0L);
+ positions.add(dataStartPosition);
+ }
+
+ @Override
+ public long getTimeUs(long position) {
+ int targetIndex =
+ Util.binarySearchFloor(
+ positions, position, /* inclusive= */ true, /* stayInBounds= */ true);
+ return timesUs.get(targetIndex);
+ }
+
+ @Override
+ public long getDataEndPosition() {
+ return dataEndPosition;
+ }
+
+ @Override
+ public boolean isSeekable() {
+ return true;
+ }
+
+ @Override
+ public long getDurationUs() {
+ return durationUs;
+ }
+
+ @Override
+ public SeekPoints getSeekPoints(long timeUs) {
+ int targetIndex =
+ Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
+ SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex));
+ if (seekPoint.timeUs >= timeUs || targetIndex == timesUs.size() - 1) {
+ return new SeekPoints(seekPoint);
+ } else {
+ SeekPoint nextSeekPoint =
+ new SeekPoint(timesUs.get(targetIndex + 1), positions.get(targetIndex + 1));
+ return new SeekPoints(seekPoint, nextSeekPoint);
+ }
+ }
+
+ /**
+ * Adds a seek point to the index if it is sufficiently distant from the other points.
+ *
+ * <p>Seek points must be added in order.
+ *
+ * @param timeUs The time corresponding to the seek point to add in microseconds.
+ * @param position The position corresponding to the seek point to add in bytes.
+ */
+ public void maybeAddSeekPoint(long timeUs, long position) {
+ if (isTimeUsInIndex(timeUs)) {
+ return;
+ }
+ timesUs.add(timeUs);
+ positions.add(position);
+ }
+
+ /**
+ * Returns whether {@code timeUs} (in microseconds) is included in the index.
+ *
+ * <p>A point is included in the index if it is equal to another point, between 2 points, or
+ * sufficiently close to the last point.
+ */
+ public boolean isTimeUsInIndex(long timeUs) {
+ long lastIndexedTimeUs = timesUs.get(timesUs.size() - 1);
+ return timeUs - lastIndexedTimeUs < MIN_TIME_BETWEEN_POINTS_US;
+ }
+
+ /* package */ void setDurationUs(long durationUs) {
+ this.durationUs = durationUs;
+ }
+}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
index a1e0e34..b9613f3 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java
@@ -21,6 +21,7 @@
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.audio.MpegAudioUtil;
+import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@@ -56,24 +57,44 @@
/**
* Flags controlling the behavior of the extractor. Possible flag values are {@link
- * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}.
+ * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, {@link #FLAG_ENABLE_INDEX_SEEKING} and {@link
+ * #FLAG_DISABLE_ID3_METADATA}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
- value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA})
+ value = {
+ FLAG_ENABLE_CONSTANT_BITRATE_SEEKING,
+ FLAG_ENABLE_INDEX_SEEKING,
+ FLAG_DISABLE_ID3_METADATA
+ })
public @interface Flags {}
/**
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
* otherwise not be possible.
+ *
+ * <p>This flag is ignored if {@link #FLAG_ENABLE_INDEX_SEEKING} is set.
*/
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
/**
+ * Flag to force index seeking, in which a time-to-byte mapping is built as the file is read.
+ *
+ * <p>This seeker may require to scan a significant portion of the file to compute a seek point.
+ * Therefore, it should only be used if one of the following is true:
+ *
+ * <ul>
+ * <li>The file is small.
+ * <li>The bitrate is variable (or it's unknown whether it's variable) and the file does not
+ * provide precise enough seeking metadata.
+ * </ul>
+ */
+ public static final int FLAG_ENABLE_INDEX_SEEKING = 1 << 1;
+ /**
* Flag to disable parsing of ID3 metadata. Can be set to save memory if ID3 metadata is not
* required.
*/
- public static final int FLAG_DISABLE_ID3_METADATA = 2;
+ public static final int FLAG_DISABLE_ID3_METADATA = 1 << 2;
/** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */
private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE =
@@ -110,21 +131,25 @@
private final MpegAudioUtil.Header synchronizedHeader;
private final GaplessInfoHolder gaplessInfoHolder;
private final Id3Peeker id3Peeker;
+ private final TrackOutput skippingTrackOutput;
- // Extractor outputs.
private @MonotonicNonNull ExtractorOutput extractorOutput;
- private @MonotonicNonNull TrackOutput trackOutput;
+ private @MonotonicNonNull TrackOutput realTrackOutput;
+ private TrackOutput currentTrackOutput; // skippingTrackOutput or realTrackOutput.
private int synchronizedHeaderData;
@Nullable private Metadata metadata;
- private @MonotonicNonNull Seeker seeker;
- private boolean disableSeeking;
private long basisTimeUs;
private long samplesRead;
private long firstSamplePosition;
private int sampleBytesRemaining;
+ private @MonotonicNonNull Seeker seeker;
+ private boolean disableSeeking;
+ private boolean isSeekInProgress;
+ private long seekTimeUs;
+
public Mp3Extractor() {
this(0);
}
@@ -149,19 +174,22 @@
gaplessInfoHolder = new GaplessInfoHolder();
basisTimeUs = C.TIME_UNSET;
id3Peeker = new Id3Peeker();
+ skippingTrackOutput = new DummyTrackOutput();
+ currentTrackOutput = skippingTrackOutput;
}
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
return synchronize(input, true);
}
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
- trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
+ realTrackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
+ currentTrackOutput = realTrackOutput;
extractorOutput.endTracks();
}
@@ -171,6 +199,11 @@
basisTimeUs = C.TIME_UNSET;
samplesRead = 0;
sampleBytesRemaining = 0;
+ seekTimeUs = timeUs;
+ if (seeker instanceof IndexSeeker && !((IndexSeeker) seeker).isTimeUsInIndex(timeUs)) {
+ isSeekInProgress = true;
+ currentTrackOutput = skippingTrackOutput;
+ }
}
@Override
@@ -179,45 +212,18 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
assertInitialized();
- if (synchronizedHeaderData == 0) {
- try {
- synchronize(input, false);
- } catch (EOFException e) {
- return RESULT_END_OF_INPUT;
+ int readResult = readInternal(input);
+ if (readResult == RESULT_END_OF_INPUT && seeker instanceof IndexSeeker) {
+ // Duration is exact when index seeker is used.
+ long durationUs = computeTimeUs(samplesRead);
+ if (seeker.getDurationUs() != durationUs) {
+ ((IndexSeeker) seeker).setDurationUs(durationUs);
+ extractorOutput.seekMap(seeker);
}
}
- if (seeker == null) {
- seeker = computeSeeker(input);
- extractorOutput.seekMap(seeker);
- trackOutput.format(
- Format.createAudioSampleFormat(
- /* id= */ null,
- synchronizedHeader.mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- MpegAudioUtil.MAX_FRAME_SIZE_BYTES,
- synchronizedHeader.channels,
- synchronizedHeader.sampleRate,
- /* pcmEncoding= */ Format.NO_VALUE,
- gaplessInfoHolder.encoderDelay,
- gaplessInfoHolder.encoderPadding,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null,
- (flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata));
- firstSamplePosition = input.getPosition();
- } else if (firstSamplePosition != 0) {
- long inputPosition = input.getPosition();
- if (inputPosition < firstSamplePosition) {
- // Skip past the seek frame.
- input.skipFully((int) (firstSamplePosition - inputPosition));
- }
- }
- return readSample(input);
+ return readResult;
}
/**
@@ -231,8 +237,41 @@
// Internal methods.
- @RequiresNonNull({"trackOutput", "seeker"})
- private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
+ @RequiresNonNull({"extractorOutput", "realTrackOutput"})
+ private int readInternal(ExtractorInput input) throws IOException {
+ if (synchronizedHeaderData == 0) {
+ try {
+ synchronize(input, false);
+ } catch (EOFException e) {
+ return RESULT_END_OF_INPUT;
+ }
+ }
+ if (seeker == null) {
+ seeker = computeSeeker(input);
+ extractorOutput.seekMap(seeker);
+ currentTrackOutput.format(
+ new Format.Builder()
+ .setSampleMimeType(synchronizedHeader.mimeType)
+ .setMaxInputSize(MpegAudioUtil.MAX_FRAME_SIZE_BYTES)
+ .setChannelCount(synchronizedHeader.channels)
+ .setSampleRate(synchronizedHeader.sampleRate)
+ .setEncoderDelay(gaplessInfoHolder.encoderDelay)
+ .setEncoderPadding(gaplessInfoHolder.encoderPadding)
+ .setMetadata((flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata)
+ .build());
+ firstSamplePosition = input.getPosition();
+ } else if (firstSamplePosition != 0) {
+ long inputPosition = input.getPosition();
+ if (inputPosition < firstSamplePosition) {
+ // Skip past the seek frame.
+ input.skipFully((int) (firstSamplePosition - inputPosition));
+ }
+ }
+ return readSample(input);
+ }
+
+ @RequiresNonNull({"realTrackOutput", "seeker"})
+ private int readSample(ExtractorInput extractorInput) throws IOException {
if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition();
if (peekEndOfStreamOrHeader(extractorInput)) {
@@ -256,8 +295,20 @@
}
}
sampleBytesRemaining = synchronizedHeader.frameSize;
+ if (seeker instanceof IndexSeeker) {
+ IndexSeeker indexSeeker = (IndexSeeker) seeker;
+ // Add seek point corresponding to the next frame instead of the current one to be able to
+ // start writing to the realTrackOutput on time when a seek is in progress.
+ indexSeeker.maybeAddSeekPoint(
+ computeTimeUs(samplesRead + synchronizedHeader.samplesPerFrame),
+ extractorInput.getPosition() + synchronizedHeader.frameSize);
+ if (isSeekInProgress && indexSeeker.isTimeUsInIndex(seekTimeUs)) {
+ isSeekInProgress = false;
+ currentTrackOutput = realTrackOutput;
+ }
+ }
}
- int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true);
+ int bytesAppended = currentTrackOutput.sampleData(extractorInput, sampleBytesRemaining, true);
if (bytesAppended == C.RESULT_END_OF_INPUT) {
return RESULT_END_OF_INPUT;
}
@@ -265,16 +316,18 @@
if (sampleBytesRemaining > 0) {
return RESULT_CONTINUE;
}
- long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate);
- trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0,
- null);
+ currentTrackOutput.sampleMetadata(
+ computeTimeUs(samplesRead), C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0, null);
samplesRead += synchronizedHeader.samplesPerFrame;
sampleBytesRemaining = 0;
return RESULT_CONTINUE;
}
- private boolean synchronize(ExtractorInput input, boolean sniffing)
- throws IOException, InterruptedException {
+ private long computeTimeUs(long samplesRead) {
+ return basisTimeUs + samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate;
+ }
+
+ private boolean synchronize(ExtractorInput input, boolean sniffing) throws IOException {
int validFrameCount = 0;
int candidateSynchronizedHeaderData = 0;
int peekedId3Bytes = 0;
@@ -351,8 +404,7 @@
* Returns whether the extractor input is peeking the end of the stream. If {@code false},
* populates the scratch buffer with the next four bytes.
*/
- private boolean peekEndOfStreamOrHeader(ExtractorInput extractorInput)
- throws IOException, InterruptedException {
+ private boolean peekEndOfStreamOrHeader(ExtractorInput extractorInput) throws IOException {
if (seeker != null) {
long dataEndPosition = seeker.getDataEndPosition();
if (dataEndPosition != C.POSITION_UNSET
@@ -368,7 +420,7 @@
}
}
- private Seeker computeSeeker(ExtractorInput input) throws IOException, InterruptedException {
+ private Seeker computeSeeker(ExtractorInput input) throws IOException {
// Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata
// takes priority as it can provide greater precision.
Seeker seekFrameSeeker = maybeReadSeekFrame(input);
@@ -379,7 +431,20 @@
}
@Nullable Seeker resultSeeker = null;
- if (metadataSeeker != null) {
+ if ((flags & FLAG_ENABLE_INDEX_SEEKING) != 0) {
+ long durationUs = C.TIME_UNSET;
+ long dataEndPosition = C.POSITION_UNSET;
+ if (metadataSeeker != null) {
+ durationUs = metadataSeeker.getDurationUs();
+ dataEndPosition = metadataSeeker.getDataEndPosition();
+ } else if (seekFrameSeeker != null) {
+ durationUs = seekFrameSeeker.getDurationUs();
+ dataEndPosition = seekFrameSeeker.getDataEndPosition();
+ }
+ resultSeeker =
+ new IndexSeeker(
+ durationUs, /* dataStartPosition= */ input.getPosition(), dataEndPosition);
+ } else if (metadataSeeker != null) {
resultSeeker = metadataSeeker;
} else if (seekFrameSeeker != null) {
resultSeeker = seekFrameSeeker;
@@ -402,11 +467,9 @@
* @return A {@link Seeker} if seeking metadata was present and valid, or {@code null} otherwise.
* @throws IOException Thrown if there was an error reading from the stream. Not expected if the
* next two frames were already peeked during synchronization.
- * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if
- * the next two frames were already peeked during synchronization.
*/
@Nullable
- private Seeker maybeReadSeekFrame(ExtractorInput input) throws IOException, InterruptedException {
+ private Seeker maybeReadSeekFrame(ExtractorInput input) throws IOException {
ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize);
input.peekFully(frame.data, 0, synchronizedHeader.frameSize);
int xingBase = (synchronizedHeader.version & 1) != 0
@@ -440,20 +503,17 @@
return seeker;
}
- /**
- * Peeks the next frame and returns a {@link ConstantBitrateSeeker} based on its bitrate.
- */
- private Seeker getConstantBitrateSeeker(ExtractorInput input)
- throws IOException, InterruptedException {
+ /** Peeks the next frame and returns a {@link ConstantBitrateSeeker} based on its bitrate. */
+ private Seeker getConstantBitrateSeeker(ExtractorInput input) throws IOException {
input.peekFully(scratch.data, 0, 4);
scratch.setPosition(0);
synchronizedHeader.setForHeaderData(scratch.readInt());
return new ConstantBitrateSeeker(input.getLength(), input.getPosition(), synchronizedHeader);
}
- @EnsuresNonNull({"extractorOutput", "trackOutput"})
+ @EnsuresNonNull({"extractorOutput", "realTrackOutput"})
private void assertInitialized() {
- Assertions.checkStateNotNull(trackOutput);
+ Assertions.checkStateNotNull(realTrackOutput);
Util.castNonNull(extractorOutput);
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
index 8f2a244..3cf8585 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java
@@ -22,6 +22,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.audio.Ac3Util;
import com.google.android.exoplayer2.audio.Ac4Util;
import com.google.android.exoplayer2.drm.DrmInitData;
@@ -445,10 +446,15 @@
long editDuration =
Util.scaleLargeTimestamp(
track.editListDurations[i], track.timescale, track.movieTimescale);
- startIndices[i] = Util.binarySearchCeil(timestamps, editMediaTime, true, true);
+ startIndices[i] =
+ Util.binarySearchFloor(
+ timestamps, editMediaTime, /* inclusive= */ true, /* stayInBounds= */ true);
endIndices[i] =
Util.binarySearchCeil(
- timestamps, editMediaTime + editDuration, omitClippedSample, false);
+ timestamps,
+ editMediaTime + editDuration,
+ /* inclusive= */ omitClippedSample,
+ /* stayInBounds= */ false);
while (startIndices[i] < endIndices[i]
&& (flags[startIndices[i]] & C.BUFFER_FLAG_KEY_FRAME) == 0) {
// Applying the edit correctly would require prerolling from the previous sync sample. In
@@ -486,7 +492,7 @@
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
long timeInSegmentUs =
Util.scaleLargeTimestamp(
- timestamps[j] - editMediaTime, C.MICROS_PER_SECOND, track.timescale);
+ Math.max(0, timestamps[j] - editMediaTime), C.MICROS_PER_SECOND, track.timescale);
editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
editedMaximumSize = sizes[j];
@@ -813,8 +819,11 @@
parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
language, out);
} else if (childAtomType == Atom.TYPE_camm) {
- out.format = Format.createSampleFormat(Integer.toString(trackId),
- MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, null);
+ out.format =
+ new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(MimeTypes.APPLICATION_CAMERA_MOTION)
+ .build();
}
stsd.setPosition(childStartPosition + childAtomSize);
}
@@ -859,17 +868,13 @@
}
out.format =
- Format.createTextSampleFormat(
- Integer.toString(trackId),
- mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- language,
- /* accessibilityChannel= */ Format.NO_VALUE,
- /* drmInitData= */ null,
- subsampleOffsetUs,
- initializationData);
+ new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setLanguage(language)
+ .setSubsampleOffsetUs(subsampleOffsetUs)
+ .setInitializationData(initializationData)
+ .build();
}
private static void parseVideoSampleEntry(
@@ -1004,22 +1009,19 @@
}
out.format =
- Format.createVideoSampleFormat(
- Integer.toString(trackId),
- mimeType,
- codecs,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- width,
- height,
- /* frameRate= */ Format.NO_VALUE,
- initializationData,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- /* colorInfo= */ null,
- drmInitData);
+ new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setCodecs(codecs)
+ .setWidth(width)
+ .setHeight(height)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .setRotationDegrees(rotationDegrees)
+ .setProjectionData(projectionData)
+ .setStereoMode(stereoMode)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .build();
}
/**
@@ -1088,6 +1090,7 @@
int channelCount;
int sampleRate;
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
+ @Nullable String codecs = null;
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
channelCount = parent.readUnsignedShort();
@@ -1184,10 +1187,10 @@
if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) {
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
// which is more reliable. See [Internal: b/10903778].
- Pair<Integer, Integer> audioSpecificConfig =
- CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData);
- sampleRate = audioSpecificConfig.first;
- channelCount = audioSpecificConfig.second;
+ AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(initializationData);
+ sampleRate = aacConfig.sampleRateHz;
+ channelCount = aacConfig.channelCount;
+ codecs = aacConfig.codecs;
}
}
} else if (childAtomType == Atom.TYPE_dac3) {
@@ -1203,9 +1206,15 @@
out.format =
Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, drmInitData);
} else if (childAtomType == Atom.TYPE_ddts) {
- out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
- Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
- language);
+ out.format =
+ new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
} else if (childAtomType == Atom.TYPE_dOps) {
// Build an Opus Identification Header (defined in RFC-7845) by concatenating the Opus Magic
// Signature and the body of the dOps atom.
@@ -1239,10 +1248,19 @@
}
if (out.format == null && mimeType != null) {
- out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
- Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
- initializationData == null ? null : Collections.singletonList(initializationData),
- drmInitData, 0, language);
+ out.format =
+ new Format.Builder()
+ .setId(trackId)
+ .setSampleMimeType(mimeType)
+ .setCodecs(codecs)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setPcmEncoding(pcmEncoding)
+ .setInitializationData(
+ initializationData == null ? null : Collections.singletonList(initializationData))
+ .setDrmInitData(drmInitData)
+ .setLanguage(language)
+ .build();
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
index 19dbe6b..84a92cf 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
@@ -114,7 +114,7 @@
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
private static final Format EMSG_FORMAT =
- Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_EMSG).build();
// Parser states.
private static final int STATE_READING_ATOM_HEADER = 0;
@@ -129,7 +129,6 @@
// Sideloaded data.
private final List<Format> closedCaptionFormats;
- @Nullable private final DrmInitData sideloadedDrmInitData;
// Track-linked data bundle, accessible as a whole through trackID.
private final SparseArray<TrackBundle> trackBundles;
@@ -185,7 +184,7 @@
* @param flags Flags that control the extractor's behavior.
*/
public FragmentedMp4Extractor(@Flags int flags) {
- this(flags, null);
+ this(flags, /* timestampAdjuster= */ null);
}
/**
@@ -193,7 +192,7 @@
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/
public FragmentedMp4Extractor(@Flags int flags, @Nullable TimestampAdjuster timestampAdjuster) {
- this(flags, timestampAdjuster, null, null);
+ this(flags, timestampAdjuster, /* sideloadedTrack= */ null, Collections.emptyList());
}
/**
@@ -201,15 +200,12 @@
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
* receive a moov box in the input data. Null if a moov box is expected.
- * @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
- * pssh boxes (if present) will be used.
*/
public FragmentedMp4Extractor(
@Flags int flags,
@Nullable TimestampAdjuster timestampAdjuster,
- @Nullable Track sideloadedTrack,
- @Nullable DrmInitData sideloadedDrmInitData) {
- this(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData, Collections.emptyList());
+ @Nullable Track sideloadedTrack) {
+ this(flags, timestampAdjuster, sideloadedTrack, Collections.emptyList());
}
/**
@@ -217,8 +213,6 @@
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
* receive a moov box in the input data. Null if a moov box is expected.
- * @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
- * pssh boxes (if present) will be used.
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
* caption channels to expose.
*/
@@ -226,10 +220,13 @@
@Flags int flags,
@Nullable TimestampAdjuster timestampAdjuster,
@Nullable Track sideloadedTrack,
- @Nullable DrmInitData sideloadedDrmInitData,
List<Format> closedCaptionFormats) {
- this(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData,
- closedCaptionFormats, null);
+ this(
+ flags,
+ timestampAdjuster,
+ sideloadedTrack,
+ closedCaptionFormats,
+ /* additionalEmsgTrackOutput= */ null);
}
/**
@@ -237,8 +234,6 @@
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
* receive a moov box in the input data. Null if a moov box is expected.
- * @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
- * pssh boxes (if present) will be used.
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
* caption channels to expose.
* @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages
@@ -249,13 +244,11 @@
@Flags int flags,
@Nullable TimestampAdjuster timestampAdjuster,
@Nullable Track sideloadedTrack,
- @Nullable DrmInitData sideloadedDrmInitData,
List<Format> closedCaptionFormats,
@Nullable TrackOutput additionalEmsgTrackOutput) {
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster;
this.sideloadedTrack = sideloadedTrack;
- this.sideloadedDrmInitData = sideloadedDrmInitData;
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
eventMessageEncoder = new EventMessageEncoder();
@@ -275,7 +268,7 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
return Sniffer.sniffFragmented(input);
}
@@ -310,8 +303,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
while (true) {
switch (parserState) {
case STATE_READING_ATOM_HEADER:
@@ -338,7 +330,7 @@
atomHeaderBytesRead = 0;
}
- private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) {
// Read the standard length atom header.
if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
@@ -426,7 +418,7 @@
return true;
}
- private void readAtomPayload(ExtractorInput input) throws IOException, InterruptedException {
+ private void readAtomPayload(ExtractorInput input) throws IOException {
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
if (atomData != null) {
input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize);
@@ -470,8 +462,7 @@
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
- DrmInitData drmInitData = sideloadedDrmInitData != null ? sideloadedDrmInitData
- : getDrmInitDataFromAtoms(moov.leafChildren);
+ @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
// Read declaration of track fragments in the Moov box.
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
@@ -550,9 +541,8 @@
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
parseMoof(moof, trackBundles, flags, scratchBytes);
- // If drm init data is sideloaded, we ignore pssh boxes.
- DrmInitData drmInitData = sideloadedDrmInitData != null ? null
- : getDrmInitDataFromAtoms(moof.leafChildren);
+
+ @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
if (drmInitData != null) {
int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) {
@@ -1179,7 +1169,7 @@
new ChunkIndex(sizes, offsets, durationsUs, timesUs));
}
- private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
+ private void readEncryptionData(ExtractorInput input) throws IOException {
TrackBundle nextTrackBundle = null;
long nextDataOffset = Long.MAX_VALUE;
int trackBundlesSize = trackBundles.size();
@@ -1217,9 +1207,8 @@
* @return Whether a sample was read. The read sample may have been output or skipped. False
* indicates that there are no samples left to read in the current mdat.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readSample(ExtractorInput input) throws IOException {
if (parserState == STATE_READING_SAMPLE_START) {
if (currentTrackBundle == null) {
@Nullable TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles);
@@ -1417,6 +1406,7 @@
}
/** Returns DrmInitData from leaf atoms. */
+ @Nullable
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
@Nullable ArrayList<SchemeData> schemeDatas = null;
int leafChildrenSize = leafChildren.size();
@@ -1512,7 +1502,9 @@
TrackEncryptionBox encryptionBox =
track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
@Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
- output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType)));
+ DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType);
+ Format format = track.format.buildUpon().setDrmInitData(updatedDrmInitData).build();
+ output.format(format);
}
/** Resets the current fragment and sample indices. */
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
index 85ca446..365e336 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java
@@ -284,25 +284,22 @@
private MetadataUtil() {}
- /**
- * Returns a {@link Format} that is the same as the input format but includes information from the
- * specified sources of metadata.
- */
- public static Format getFormatWithMetadata(
+ /** Updates a {@link Format.Builder} to include metadata from the provided sources. */
+ public static void setFormatMetadata(
int trackType,
- Format format,
@Nullable Metadata udtaMetadata,
@Nullable Metadata mdtaMetadata,
- GaplessInfoHolder gaplessInfoHolder) {
+ GaplessInfoHolder gaplessInfoHolder,
+ Format.Builder formatBuilder) {
if (trackType == C.TRACK_TYPE_AUDIO) {
if (gaplessInfoHolder.hasGaplessInfo()) {
- format =
- format.copyWithGaplessInfo(
- gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding);
+ formatBuilder
+ .setEncoderDelay(gaplessInfoHolder.encoderDelay)
+ .setEncoderPadding(gaplessInfoHolder.encoderPadding);
}
// We assume all udta metadata is associated with the audio track.
if (udtaMetadata != null) {
- format = format.copyWithMetadata(udtaMetadata);
+ formatBuilder.setMetadata(udtaMetadata);
}
} else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) {
// Populate only metadata keys that are known to be specific to video.
@@ -311,12 +308,11 @@
if (entry instanceof MdtaMetadataEntry) {
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key)) {
- format = format.copyWithMetadata(new Metadata(mdtaMetadataEntry));
+ formatBuilder.setMetadata(new Metadata(mdtaMetadataEntry));
}
}
}
}
- return format;
}
/**
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
index c35c23d..48c7e3e 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java
@@ -146,7 +146,7 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
return Sniffer.sniffUnfragmented(input);
}
@@ -176,8 +176,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
while (true) {
switch (parserState) {
case STATE_READING_ATOM_HEADER:
@@ -270,7 +269,7 @@
atomHeaderBytesRead = 0;
}
- private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) {
// Read the standard length atom header.
if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
@@ -341,7 +340,7 @@
* restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped.
*/
private boolean readAtomPayload(ExtractorInput input, PositionHolder positionHolder)
- throws IOException, InterruptedException {
+ throws IOException {
long atomPayloadSize = atomSize - atomHeaderBytesRead;
long atomEndPosition = input.getPosition() + atomPayloadSize;
boolean seekRequired = false;
@@ -423,17 +422,17 @@
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
- Format format = track.format.copyWithMaxInputSize(maxInputSize);
+ Format.Builder formatBuilder = track.format.buildUpon();
+ formatBuilder.setMaxInputSize(maxInputSize);
if (track.type == C.TRACK_TYPE_VIDEO
&& trackDurationUs > 0
&& trackSampleTable.sampleCount > 1) {
float frameRate = trackSampleTable.sampleCount / (trackDurationUs / 1000000f);
- format = format.copyWithFrameRate(frameRate);
+ formatBuilder.setFrameRate(frameRate);
}
- format =
- MetadataUtil.getFormatWithMetadata(
- track.type, format, udtaMetadata, mdtaMetadata, gaplessInfoHolder);
- mp4Track.trackOutput.format(format);
+ MetadataUtil.setFormatMetadata(
+ track.type, udtaMetadata, mdtaMetadata, gaplessInfoHolder, formatBuilder);
+ mp4Track.trackOutput.format(formatBuilder.build());
if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) {
firstVideoTrackIndex = tracks.size();
@@ -485,22 +484,20 @@
/**
* Attempts to extract the next sample in the current mdat atom for the specified track.
- * <p>
- * Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in
- * {@code positionHolder}.
- * <p>
- * Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns
- * {@link #RESULT_CONTINUE}.
+ *
+ * <p>Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in {@code
+ * positionHolder}.
+ *
+ * <p>Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns {@link
+ * #RESULT_CONTINUE}.
*
* @param input The {@link ExtractorInput} from which to read data.
* @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread is interrupted.
*/
- private int readSample(ExtractorInput input, PositionHolder positionHolder)
- throws IOException, InterruptedException {
+ private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException {
long inputPosition = input.getPosition();
if (sampleTrackIndex == C.INDEX_UNSET) {
sampleTrackIndex = getTrackIndexOfNextReadSample(inputPosition);
@@ -663,8 +660,7 @@
* we can't rely on the file type though. Instead we must check the 8 bytes after the common
* header bytes ourselves.
*/
- private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input)
- throws IOException, InterruptedException {
+ private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException {
scratch.reset(8);
// Peek the next 8 bytes which can be either
// (iso) [1 byte version + 3 bytes flags][4 byte size of next atom]
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java
index dac74bf..c661e7b 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java
@@ -66,10 +66,8 @@
* @param input The extractor input from which to peek data. The peek position will be modified.
* @return Whether the input appears to be in the fragmented MP4 format.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- public static boolean sniffFragmented(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static boolean sniffFragmented(ExtractorInput input) throws IOException {
return sniffInternal(input, true);
}
@@ -80,15 +78,13 @@
* @param input The extractor input from which to peek data. The peek position will be modified.
* @return Whether the input appears to be in the unfragmented MP4 format.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- public static boolean sniffUnfragmented(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static boolean sniffUnfragmented(ExtractorInput input) throws IOException {
return sniffInternal(input, false);
}
private static boolean sniffInternal(ExtractorInput input, boolean fragmented)
- throws IOException, InterruptedException {
+ throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH
? SEARCH_LENGTH : inputLength);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java
index b9f51b7..456cd50 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java
@@ -169,7 +169,7 @@
*
* @param input An {@link ExtractorInput} from which to read the encryption data.
*/
- public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
+ public void fillEncryptionData(ExtractorInput input) throws IOException {
input.readFully(sampleEncryptionData.data, 0, sampleEncryptionData.limit());
sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java
index ecbaaeb..1d73a1b 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java
@@ -88,7 +88,7 @@
}
@Override
- public long read(ExtractorInput input) throws IOException, InterruptedException {
+ public long read(ExtractorInput input) throws IOException {
switch (state) {
case STATE_IDLE:
return -1;
@@ -148,9 +148,8 @@
* @return The byte position from which data should be provided for the next step, or {@link
* C#POSITION_UNSET} if the search has converged.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
- private long getNextSeekPosition(ExtractorInput input) throws IOException, InterruptedException {
+ private long getNextSeekPosition(ExtractorInput input) throws IOException {
if (start == end) {
return C.POSITION_UNSET;
}
@@ -199,10 +198,8 @@
* @param input The {@link ExtractorInput} to read from.
* @throws ParserException If populating the page header fails.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
- private void skipToPageOfTargetGranule(ExtractorInput input)
- throws IOException, InterruptedException {
+ private void skipToPageOfTargetGranule(ExtractorInput input) throws IOException {
pageHeader.populate(input, /* quiet= */ false);
while (pageHeader.granulePosition <= targetGranule) {
input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
@@ -218,11 +215,10 @@
*
* @param input The {@code ExtractorInput} to skip to the next page.
* @throws IOException If peeking/reading from the input fails.
- * @throws InterruptedException If the thread is interrupted.
* @throws EOFException If the next page can't be found before the end of the input.
*/
@VisibleForTesting
- void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException {
+ void skipToNextPage(ExtractorInput input) throws IOException {
if (!skipToNextPage(input, payloadEndPosition)) {
// Not found until eof.
throw new EOFException();
@@ -236,10 +232,8 @@
* @param limit The limit up to which the search should take place.
* @return Whether the next page was found.
* @throws IOException If peeking/reading from the input fails.
- * @throws InterruptedException If interrupted while peeking/reading from the input.
*/
- private boolean skipToNextPage(ExtractorInput input, long limit)
- throws IOException, InterruptedException {
+ private boolean skipToNextPage(ExtractorInput input, long limit) throws IOException {
limit = Math.min(limit + 3, payloadEndPosition);
byte[] buffer = new byte[2048];
int peekLength = buffer.length;
@@ -275,10 +269,9 @@
* @param input The {@link ExtractorInput} to read from.
* @return The total number of samples of this input.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If the thread is interrupted.
*/
@VisibleForTesting
- long readGranuleOfLastPage(ExtractorInput input) throws IOException, InterruptedException {
+ long readGranuleOfLastPage(ExtractorInput input) throws IOException {
skipToNextPage(input);
pageHeader.reset();
while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < payloadEndPosition) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java
index 47698cc..9aaa333 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java
@@ -44,7 +44,7 @@
private boolean streamReaderInitialized;
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
try {
return sniffInternal(input);
} catch (ParserException e) {
@@ -70,8 +70,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(output); // Asserts that init has been called.
if (streamReader == null) {
if (!sniffInternal(input)) {
@@ -89,7 +88,7 @@
}
@EnsuresNonNullIf(expression = "streamReader", result = true)
- private boolean sniffInternal(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean sniffInternal(ExtractorInput input) throws IOException {
OggPageHeader header = new OggPageHeader();
if (!header.populate(input, true) || (header.type & 0x02) != 0x02) {
return false;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java
index 9c594ff..2ee65f0 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java
@@ -55,9 +55,8 @@
* @return {@code true} if the read was successful. The read fails if the end of the input is
* encountered without reading data.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If the thread is interrupted.
*/
- public boolean populate(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean populate(ExtractorInput input) throws IOException {
Assertions.checkState(input != null);
if (populated) {
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java
index c7fb3ff..d96aaa4 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java
@@ -82,10 +82,8 @@
* @return Whether the read was successful. The read fails if the end of the input is encountered
* without reading data.
* @throws IOException If reading data fails or the stream is invalid.
- * @throws InterruptedException If the thread is interrupted.
*/
- public boolean populate(ExtractorInput input, boolean quiet)
- throws IOException, InterruptedException {
+ public boolean populate(ExtractorInput input, boolean quiet) throws IOException {
scratch.reset();
reset();
boolean hasEnoughBytes = input.getLength() == C.LENGTH_UNSET
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java
index 1fa7478..7626aa5 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java
@@ -43,17 +43,15 @@
/**
* Reads data from the {@link ExtractorInput} to build the {@link SeekMap} or to continue a seek.
- * <p/>
- * If more data is required or if the position of the input needs to be modified then a position
- * from which data should be provided is returned. Else a negative value is returned. If a seek
- * has been completed then the value returned is -(currentGranule + 2). Else it is -1.
+ *
+ * <p>If more data is required or if the position of the input needs to be modified then a
+ * position from which data should be provided is returned. Else a negative value is returned. If
+ * a seek has been completed then the value returned is -(currentGranule + 2). Else it is -1.
*
* @param input The {@link ExtractorInput} to read from.
* @return A non-negative position to seek the {@link ExtractorInput} to, or -(currentGranule + 2)
* if the progressive seek has completed, or -1 otherwise.
* @throws IOException If reading from the {@link ExtractorInput} fails.
- * @throws InterruptedException If the thread is interrupted.
*/
- long read(ExtractorInput input) throws IOException, InterruptedException;
-
+ long read(ExtractorInput input) throws IOException;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java
index 84a7ecc..018fd94 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java
@@ -77,18 +77,12 @@
putNativeOrderLong(initializationData, DEFAULT_SEEK_PRE_ROLL_SAMPLES);
setupData.format =
- Format.createAudioSampleFormat(
- null,
- MimeTypes.AUDIO_OPUS,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- SAMPLE_RATE,
- initializationData,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_OPUS)
+ .setChannelCount(channelCount)
+ .setSampleRate(SAMPLE_RATE)
+ .setInitializationData(initializationData)
+ .build();
headerRead = true;
} else {
boolean headerPacket = packet.readInt() == OPUS_CODE;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java
index c701339..f28602d 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java
@@ -101,11 +101,8 @@
}
}
- /**
- * @see Extractor#read(ExtractorInput, PositionHolder)
- */
- final int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ /** @see Extractor#read(ExtractorInput, PositionHolder) */
+ final int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
switch (state) {
case STATE_READ_HEADERS:
return readHeaders(input);
@@ -121,7 +118,7 @@
}
}
- private int readHeaders(ExtractorInput input) throws IOException, InterruptedException {
+ private int readHeaders(ExtractorInput input) throws IOException {
boolean readingHeaders = true;
while (readingHeaders) {
if (!oggPacket.populate(input)) {
@@ -166,8 +163,7 @@
return Extractor.RESULT_CONTINUE;
}
- private int readPayload(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ private int readPayload(ExtractorInput input, PositionHolder seekPosition) throws IOException {
long position = oggSeeker.read(input);
if (position >= 0) {
seekPosition.position = position;
@@ -238,8 +234,8 @@
* @param setupData Setup data to be filled.
* @return Whether the packet contains header data.
*/
- protected abstract boolean readHeaders(ParsableByteArray packet, long position,
- SetupData setupData) throws IOException, InterruptedException;
+ protected abstract boolean readHeaders(
+ ParsableByteArray packet, long position, SetupData setupData) throws IOException;
/**
* Called on end of seeking.
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java
index 5c573e2..d6faa90 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java
@@ -99,23 +99,21 @@
return true;
}
- ArrayList<byte[]> codecInitialisationData = new ArrayList<>();
- codecInitialisationData.add(vorbisSetup.idHeader.data);
- codecInitialisationData.add(vorbisSetup.setupHeaderData);
+ VorbisUtil.VorbisIdHeader idHeader = vorbisSetup.idHeader;
+
+ ArrayList<byte[]> codecInitializationData = new ArrayList<>();
+ codecInitializationData.add(idHeader.data);
+ codecInitializationData.add(vorbisSetup.setupHeaderData);
setupData.format =
- Format.createAudioSampleFormat(
- null,
- MimeTypes.AUDIO_VORBIS,
- /* codecs= */ null,
- this.vorbisSetup.idHeader.bitrateNominal,
- Format.NO_VALUE,
- this.vorbisSetup.idHeader.channels,
- (int) this.vorbisSetup.idHeader.sampleRate,
- codecInitialisationData,
- null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_VORBIS)
+ .setAverageBitrate(idHeader.bitrateNominal)
+ .setPeakBitrate(idHeader.bitrateMaximum)
+ .setChannelCount(idHeader.channels)
+ .setSampleRate(idHeader.sampleRate)
+ .setInitializationData(codecInitializationData)
+ .build();
return true;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java
index c2cf2b1..ae30231 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java
@@ -71,15 +71,14 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
dataScratch.reset();
input.peekFully(dataScratch.data, 0, HEADER_SIZE);
return dataScratch.readInt() == HEADER_ID;
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(trackOutput); // Asserts that init has been called.
while (true) {
switch (parserState) {
@@ -118,7 +117,7 @@
// Do nothing
}
- private boolean parseHeader(ExtractorInput input) throws IOException, InterruptedException {
+ private boolean parseHeader(ExtractorInput input) throws IOException {
dataScratch.reset();
if (input.readFully(dataScratch.data, 0, HEADER_SIZE, true)) {
if (dataScratch.readInt() != HEADER_ID) {
@@ -132,8 +131,7 @@
}
}
- private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException,
- InterruptedException {
+ private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException {
dataScratch.reset();
if (version == 0) {
if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V0 + 1, true)) {
@@ -156,7 +154,7 @@
}
@RequiresNonNull("trackOutput")
- private void parseSamples(ExtractorInput input) throws IOException, InterruptedException {
+ private void parseSamples(ExtractorInput input) throws IOException {
for (; remainingSampleCount > 0; remainingSampleCount--) {
dataScratch.reset();
input.readFully(dataScratch.data, 0, 3);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java
index b1d15b7..f0cb8ca 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java
@@ -61,7 +61,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
// Skip any ID3 headers.
ParsableByteArray scratch = new ParsableByteArray(ID3_HEADER_LENGTH);
int startPosition = 0;
@@ -124,8 +124,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
- InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
int bytesRead = input.read(sampleData.data, 0, MAX_SYNC_FRAME_SIZE);
if (bytesRead == C.RESULT_END_OF_INPUT) {
return RESULT_END_OF_INPUT;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java
index 1d88c2b..b025be9 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java
@@ -199,20 +199,15 @@
if (format == null
|| frameInfo.channelCount != format.channelCount
|| frameInfo.sampleRate != format.sampleRate
- || Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) {
+ || !Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) {
format =
- Format.createAudioSampleFormat(
- formatId,
- frameInfo.mimeType,
- null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- frameInfo.channelCount,
- frameInfo.sampleRate,
- null,
- null,
- 0,
- language);
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(frameInfo.mimeType)
+ .setChannelCount(frameInfo.channelCount)
+ .setSampleRate(frameInfo.sampleRate)
+ .setLanguage(language)
+ .build();
output.format(format);
}
sampleSize = frameInfo.frameSize;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java
index 205d71e..c493d1d 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java
@@ -68,7 +68,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
// Skip any ID3 headers.
ParsableByteArray scratch = new ParsableByteArray(ID3_HEADER_LENGTH);
int startPosition = 0;
@@ -132,8 +132,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
int bytesRead = input.read(sampleData.data, /* offset= */ 0, /* length= */ READ_BUFFER_SIZE);
if (bytesRead == C.RESULT_END_OF_INPUT) {
return RESULT_END_OF_INPUT;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java
index 5b4f17e..517a233 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java
@@ -199,18 +199,13 @@
|| frameInfo.sampleRate != format.sampleRate
|| !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
format =
- Format.createAudioSampleFormat(
- formatId,
- MimeTypes.AUDIO_AC4,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- frameInfo.channelCount,
- frameInfo.sampleRate,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- language);
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.AUDIO_AC4)
+ .setChannelCount(frameInfo.channelCount)
+ .setSampleRate(frameInfo.sampleRate)
+ .setLanguage(language)
+ .build();
output.format(format);
}
sampleSize = frameInfo.frameSize;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java
index e481a84..f870527 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java
@@ -120,7 +120,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
// Skip any ID3 headers.
int startPosition = peekId3Header(input);
@@ -179,8 +179,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(extractorOutput); // Asserts that init has been called.
long inputLength = input.getLength();
@@ -212,7 +211,7 @@
return RESULT_CONTINUE;
}
- private int peekId3Header(ExtractorInput input) throws IOException, InterruptedException {
+ private int peekId3Header(ExtractorInput input) throws IOException {
int firstFramePosition = 0;
while (true) {
input.peekFully(scratch.data, /* offset= */ 0, ID3_HEADER_LENGTH);
@@ -256,8 +255,7 @@
hasOutputSeekMap = true;
}
- private void calculateAverageFrameSize(ExtractorInput input)
- throws IOException, InterruptedException {
+ private void calculateAverageFrameSize(ExtractorInput input) throws IOException {
if (hasCalculatedAverageFrameSize) {
return;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java
index 053d084..59ab659 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java
@@ -15,17 +15,16 @@
*/
package com.google.android.exoplayer2.extractor.ts;
-import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
@@ -140,8 +139,11 @@
if (exposeId3) {
idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
- id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
- MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
+ id3Output.format(
+ new Format.Builder()
+ .setId(idGenerator.getFormatId())
+ .setSampleMimeType(MimeTypes.APPLICATION_ID3)
+ .build());
} else {
id3Output = new DummyTrackOutput();
}
@@ -353,42 +355,44 @@
}
/**
- * Returns whether the given syncPositionCandidate is a real SYNC word.
- *
- * <p>SYNC word pattern can occur within AAC data, so we perform a few checks to make sure this is
- * really a SYNC word. This includes:
+ * Checks whether a candidate SYNC word position is likely to be the position of a real SYNC word.
+ * The caller must check that the first byte of the SYNC word is 0xFF before calling this method.
+ * This method performs the following checks:
*
* <ul>
- * <li>Checking if MPEG version of this frame matches the first detected version.
- * <li>Checking if the sample rate index of this frame matches the first detected sample rate
- * index.
- * <li>Checking if the bytes immediately after the current package also match a SYNC-word.
+ * <li>The MPEG version of this frame must match the previously detected version.
+ * <li>The sample rate index of this frame must match the previously detected sample rate index.
+ * <li>The frame size must be at least 7 bytes
+ * <li>The bytes following the frame must be either another SYNC word with the same MPEG
+ * version, or the start of an ID3 header.
* </ul>
*
- * If the buffer runs out of data for any check, optimistically skip that check, because
- * AdtsReader consumes each buffer as a whole. We will still run a header validity check later.
+ * With the exception of the first check, if there is insufficient data in the buffer then checks
+ * are optimistically skipped and {@code true} is returned.
+ *
+ * @param pesBuffer The buffer containing at data to check.
+ * @param syncPositionCandidate The candidate SYNC word position. May be -1 if the first byte of
+ * the candidate was the last byte of the previously consumed buffer.
+ * @return True if all checks were passed or skipped, indicating the position is likely to be the
+ * position of a real SYNC word. False otherwise.
*/
private boolean checkSyncPositionValid(ParsableByteArray pesBuffer, int syncPositionCandidate) {
- // The SYNC word contains 2 bytes, and the first byte may be in the previously consumed buffer.
- // Hence the second byte of the SYNC word may be byte 0 of this buffer, and
- // syncPositionCandidate (which indicates position of the first byte of the SYNC word) may be
- // -1.
- // Since the first byte of the SYNC word is always FF, which does not contain any informational
- // bits, we set the byte position to be the second byte in the SYNC word to ensure it's always
- // within this buffer.
pesBuffer.setPosition(syncPositionCandidate + 1);
if (!tryRead(pesBuffer, adtsScratch.data, 1)) {
return false;
}
+ // The MPEG version of this frame must match the previously detected version.
adtsScratch.setPosition(4);
int currentFrameVersion = adtsScratch.readBits(1);
if (firstFrameVersion != VERSION_UNSET && currentFrameVersion != firstFrameVersion) {
return false;
}
+ // The sample rate index of this frame must match the previously detected sample rate index.
if (firstFrameSampleRateIndex != C.INDEX_UNSET) {
if (!tryRead(pesBuffer, adtsScratch.data, 1)) {
+ // Insufficient data for further checks.
return true;
}
adtsScratch.setPosition(2);
@@ -399,24 +403,50 @@
pesBuffer.setPosition(syncPositionCandidate + 2);
}
- // Optionally check the byte after this frame matches SYNC word.
-
+ // The frame size must be at least 7 bytes.
if (!tryRead(pesBuffer, adtsScratch.data, 4)) {
+ // Insufficient data for further checks.
return true;
}
adtsScratch.setPosition(14);
int frameSize = adtsScratch.readBits(13);
- if (frameSize <= 6) {
- // Not a frame.
+ if (frameSize < 7) {
return false;
}
+
+ // The bytes following the frame must be either another SYNC word with the same MPEG version, or
+ // the start of an ID3 header.
+ byte[] data = pesBuffer.data;
+ int dataLimit = pesBuffer.limit();
int nextSyncPosition = syncPositionCandidate + frameSize;
- if (nextSyncPosition + 1 >= pesBuffer.limit()) {
+ if (nextSyncPosition >= dataLimit) {
+ // Insufficient data for further checks.
return true;
}
- return (isAdtsSyncBytes(pesBuffer.data[nextSyncPosition], pesBuffer.data[nextSyncPosition + 1])
- && (firstFrameVersion == VERSION_UNSET
- || ((pesBuffer.data[nextSyncPosition + 1] & 0x8) >> 3) == currentFrameVersion));
+ if (data[nextSyncPosition] == (byte) 0xFF) {
+ if (nextSyncPosition + 1 == dataLimit) {
+ // Insufficient data for further checks.
+ return true;
+ }
+ return isAdtsSyncBytes((byte) 0xFF, data[nextSyncPosition + 1])
+ && ((data[nextSyncPosition + 1] & 0x8) >> 3) == currentFrameVersion;
+ } else {
+ if (data[nextSyncPosition] != 'I') {
+ return false;
+ }
+ if (nextSyncPosition + 1 == dataLimit) {
+ // Insufficient data for further checks.
+ return true;
+ }
+ if (data[nextSyncPosition + 1] != 'D') {
+ return false;
+ }
+ if (nextSyncPosition + 2 == dataLimit) {
+ // Insufficient data for further checks.
+ return true;
+ }
+ return data[nextSyncPosition + 2] == '3';
+ }
}
private boolean isAdtsSyncBytes(byte firstByte, byte secondByte) {
@@ -467,14 +497,19 @@
int channelConfig = adtsScratch.readBits(3);
byte[] audioSpecificConfig =
- CodecSpecificDataUtil.buildAacAudioSpecificConfig(
+ AacUtil.buildAudioSpecificConfig(
audioObjectType, firstFrameSampleRateIndex, channelConfig);
- Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
- audioSpecificConfig);
-
- Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
- Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
- Collections.singletonList(audioSpecificConfig), null, 0, language);
+ AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(audioSpecificConfig);
+ Format format =
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setCodecs(aacConfig.codecs)
+ .setChannelCount(aacConfig.channelCount)
+ .setSampleRate(aacConfig.sampleRateHz)
+ .setInitializationData(Collections.singletonList(audioSpecificConfig))
+ .setLanguage(language)
+ .build();
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second.
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java
index ad1e8cc..c48c790 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java
@@ -80,7 +80,10 @@
* delimiters (AUDs).
*/
public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
- /** Prevents the creation of {@link SpliceInfoSectionReader} instances. */
+ /**
+ * Prevents the creation of {@link SectionPayloadReader}s for splice information sections
+ * (SCTE-35).
+ */
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
/**
* Whether the list of {@code closedCaptionFormats} passed to {@link
@@ -112,7 +115,7 @@
this(
flags,
Collections.singletonList(
- Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null)));
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_CEA608).build()));
}
/**
@@ -135,8 +138,8 @@
return new SparseArray<>();
}
- @Nullable
@Override
+ @Nullable
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA:
@@ -170,22 +173,25 @@
return new PesReader(new H265Reader(buildSeiReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
- ? null : new SectionReader(new SpliceInfoSectionReader());
+ ? null
+ : new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_SCTE35));
case TsExtractor.TS_STREAM_TYPE_ID3:
return new PesReader(new Id3Reader());
case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
return new PesReader(
new DvbSubtitleReader(esInfo.dvbSubtitleInfos));
+ case TsExtractor.TS_STREAM_TYPE_AIT:
+ return new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_AIT));
default:
return null;
}
}
/**
- * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link SeiReader} for
- * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a
- * {@link SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor
- * is not present.
+ * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link SeiReader} for {@link
+ * #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a {@link
+ * SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor is not
+ * present.
*
* @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}.
* @return A {@link SeiReader} for closed caption tracks.
@@ -258,17 +264,12 @@
}
closedCaptionFormats.add(
- Format.createTextSampleFormat(
- /* id= */ null,
- mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- language,
- accessibilityChannel,
- /* drmInitData= */ null,
- Format.OFFSET_SAMPLE_RELATIVE,
- initializationData));
+ new Format.Builder()
+ .setSampleMimeType(mimeType)
+ .setLanguage(language)
+ .setAccessibilityChannel(accessibilityChannel)
+ .setInitializationData(initializationData)
+ .build());
}
} else {
// Unknown descriptor. Ignore.
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java
index 146f663..9baaf85 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java
@@ -61,15 +61,12 @@
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(
- Format.createImageSampleFormat(
- idGenerator.getFormatId(),
- MimeTypes.APPLICATION_DVBSUBS,
- /* codecs= */ null,
- Format.NO_VALUE,
- /* selectionFlags= */ 0,
- Collections.singletonList(subtitleInfo.initializationData),
- subtitleInfo.language,
- /* drmInitData= */ null));
+ new Format.Builder()
+ .setId(idGenerator.getFormatId())
+ .setSampleMimeType(MimeTypes.APPLICATION_DVBSUBS)
+ .setInitializationData(Collections.singletonList(subtitleInfo.initializationData))
+ .setLanguage(subtitleInfo.language)
+ .build());
outputs[i] = output;
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java
index 42377ec..012de81 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java
@@ -245,9 +245,15 @@
break;
}
- Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
- Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
- Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
+ Format format =
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.VIDEO_MPEG2)
+ .setWidth(width)
+ .setHeight(height)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .setInitializationData(Collections.singletonList(csdData))
+ .build();
long frameDurationUs = 0;
int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java
index ed38567..55f5fb3 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java
@@ -201,23 +201,21 @@
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
+ String codecs =
+ CodecSpecificDataUtil.buildAvcCodecString(
+ spsData.profileIdc,
+ spsData.constraintsFlagsAndReservedZero2Bits,
+ spsData.levelIdc);
output.format(
- Format.createVideoSampleFormat(
- formatId,
- MimeTypes.VIDEO_H264,
- CodecSpecificDataUtil.buildAvcCodecString(
- spsData.profileIdc,
- spsData.constraintsFlagsAndReservedZero2Bits,
- spsData.levelIdc),
- /* bitrate= */ Format.NO_VALUE,
- /* maxInputSize= */ Format.NO_VALUE,
- spsData.width,
- spsData.height,
- /* frameRate= */ Format.NO_VALUE,
- initializationData,
- /* rotationDegrees= */ Format.NO_VALUE,
- spsData.pixelWidthAspectRatio,
- /* drmInitData= */ null));
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setCodecs(codecs)
+ .setWidth(spsData.width)
+ .setHeight(spsData.height)
+ .setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio)
+ .setInitializationData(initializationData)
+ .build());
hasOutputFormat = true;
sampleReader.putSps(spsData);
sampleReader.putPps(ppsData);
@@ -560,28 +558,32 @@
}
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
+ if (!isComplete) {
+ return false;
+ }
+ if (!other.isComplete) {
+ return true;
+ }
// See ISO 14496-10 subsection 7.4.1.2.4.
SpsData spsData = Assertions.checkStateNotNull(this.spsData);
SpsData otherSpsData = Assertions.checkStateNotNull(other.spsData);
- return isComplete
- && (!other.isComplete
- || frameNum != other.frameNum
- || picParameterSetId != other.picParameterSetId
- || fieldPicFlag != other.fieldPicFlag
- || (bottomFieldFlagPresent
- && other.bottomFieldFlagPresent
- && bottomFieldFlag != other.bottomFieldFlag)
- || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
- || (spsData.picOrderCountType == 0
- && otherSpsData.picOrderCountType == 0
- && (picOrderCntLsb != other.picOrderCntLsb
- || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
- || (spsData.picOrderCountType == 1
- && otherSpsData.picOrderCountType == 1
- && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
- || deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
- || idrPicFlag != other.idrPicFlag
- || (idrPicFlag && idrPicId != other.idrPicId));
+ return frameNum != other.frameNum
+ || picParameterSetId != other.picParameterSetId
+ || fieldPicFlag != other.fieldPicFlag
+ || (bottomFieldFlagPresent
+ && other.bottomFieldFlagPresent
+ && bottomFieldFlag != other.bottomFieldFlag)
+ || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
+ || (spsData.picOrderCountType == 0
+ && otherSpsData.picOrderCountType == 0
+ && (picOrderCntLsb != other.picOrderCntLsb
+ || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
+ || (spsData.picOrderCountType == 1
+ && otherSpsData.picOrderCountType == 1
+ && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
+ || deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
+ || idrPicFlag != other.idrPicFlag
+ || (idrPicFlag && idrPicId != other.idrPicId);
}
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
index 098d19c..4369ff7 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
@@ -233,10 +233,10 @@
NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data.
- byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
- System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
- System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.nalLength);
- System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength);
+ byte[] csdData = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
+ System.arraycopy(vps.nalData, 0, csdData, 0, vps.nalLength);
+ System.arraycopy(sps.nalData, 0, csdData, vps.nalLength, sps.nalLength);
+ System.arraycopy(pps.nalData, 0, csdData, vps.nalLength + sps.nalLength, pps.nalLength);
// Parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
@@ -336,9 +336,14 @@
}
}
- return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
- Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
- Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
+ return new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.VIDEO_H265)
+ .setWidth(picWidthInLumaSamples)
+ .setHeight(picHeightInLumaSamples)
+ .setPixelWidthHeightRatio(pixelWidthHeightRatio)
+ .setInitializationData(Collections.singletonList(csdData))
+ .build();
}
/**
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java
index b2fa29d..28c5489 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java
@@ -61,8 +61,11 @@
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
- output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3,
- null, Format.NO_VALUE, null));
+ output.format(
+ new Format.Builder()
+ .setId(idGenerator.getFormatId())
+ .setSampleMimeType(MimeTypes.APPLICATION_ID3)
+ .build());
}
@Override
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java
index 4cb1dc5..3465d89 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java
@@ -15,16 +15,15 @@
*/
package com.google.android.exoplayer2.extractor.ts;
-import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
@@ -72,6 +71,7 @@
private int sampleRateHz;
private long sampleDurationUs;
private int channelCount;
+ @Nullable private String codecs;
/**
* @param language Track language.
@@ -203,18 +203,15 @@
byte[] initData = new byte[(readBits + 7) / 8];
data.readBits(initData, 0, readBits);
Format format =
- Format.createAudioSampleFormat(
- formatId,
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- Format.NO_VALUE,
- Format.NO_VALUE,
- channelCount,
- sampleRateHz,
- Collections.singletonList(initData),
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- language);
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setCodecs(codecs)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRateHz)
+ .setInitializationData(Collections.singletonList(initData))
+ .setLanguage(language)
+ .build();
if (!format.equals(this.format)) {
this.format = format;
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
@@ -273,9 +270,10 @@
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
int bitsLeft = data.bitsLeft();
- Pair<Integer, Integer> config = CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, true);
- sampleRateHz = config.first;
- channelCount = config.second;
+ AacUtil.Config config = AacUtil.parseAudioSpecificConfig(data, /* forceReadToEnd= */ true);
+ codecs = config.codecs;
+ sampleRateHz = config.sampleRateHz;
+ channelCount = config.channelCount;
return bitsLeft - data.bitsLeft();
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java
index 4edf93d..44870c3 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java
@@ -188,18 +188,14 @@
if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
Format format =
- Format.createAudioSampleFormat(
- formatId,
- header.mimeType,
- null,
- Format.NO_VALUE,
- MpegAudioUtil.MAX_FRAME_SIZE_BYTES,
- header.channels,
- header.sampleRate,
- null,
- null,
- 0,
- language);
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(header.mimeType)
+ .setMaxInputSize(MpegAudioUtil.MAX_FRAME_SIZE_BYTES)
+ .setChannelCount(header.channels)
+ .setSampleRate(header.sampleRate)
+ .setLanguage(language)
+ .build();
output.format(format);
hasOutputFormat = true;
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java
new file mode 100644
index 0000000..af374f6
--- /dev/null
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.extractor.ts;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.extractor.ExtractorOutput;
+import com.google.android.exoplayer2.extractor.TrackOutput;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.util.TimestampAdjuster;
+import com.google.android.exoplayer2.util.Util;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/**
+ * A {@link SectionPayloadReader} that directly outputs the section bytes as sample data.
+ *
+ * <p>Timestamp adjustment is provided through {@link Format#subsampleOffsetUs}.
+ */
+public final class PassthroughSectionPayloadReader implements SectionPayloadReader {
+
+ private final String mimeType;
+ private @MonotonicNonNull TimestampAdjuster timestampAdjuster;
+ private @MonotonicNonNull TrackOutput output;
+ private boolean formatDeclared;
+
+ /**
+ * Create a new PassthroughSectionPayloadReader.
+ *
+ * @param mimeType The MIME type set as {@link Format#sampleMimeType} on the created output track.
+ */
+ public PassthroughSectionPayloadReader(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ @Override
+ public void init(
+ TimestampAdjuster timestampAdjuster,
+ ExtractorOutput extractorOutput,
+ TsPayloadReader.TrackIdGenerator idGenerator) {
+ this.timestampAdjuster = timestampAdjuster;
+ idGenerator.generateNewId();
+ output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
+ }
+
+ @Override
+ public void consume(ParsableByteArray sectionData) {
+ assertInitialized();
+ if (!formatDeclared) {
+ if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
+ // There is not enough information to initialize the timestamp adjuster.
+ return;
+ }
+ output.format(
+ new Format.Builder()
+ .setSampleMimeType(mimeType)
+ .setSubsampleOffsetUs(timestampAdjuster.getTimestampOffsetUs())
+ .build());
+ formatDeclared = true;
+ }
+ int sampleSize = sectionData.bytesLeft();
+ output.sampleData(sectionData, sampleSize);
+ output.sampleMetadata(
+ timestampAdjuster.getLastAdjustedTimestampUs(),
+ C.BUFFER_FLAG_KEY_FRAME,
+ sampleSize,
+ 0,
+ null);
+ }
+
+ @EnsuresNonNull({"timestampAdjuster", "output"})
+ private void assertInitialized() {
+ Assertions.checkStateNotNull(timestampAdjuster);
+ Util.castNonNull(output);
+ }
+}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java
index c4f53ba..09cf9b3 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java
@@ -70,7 +70,7 @@
@Override
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
- throws IOException, InterruptedException {
+ throws IOException {
long inputPosition = input.getPosition();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java
index b0cdf7e..4748b83 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java
@@ -81,11 +81,9 @@
* to hold the position of the required seek.
* @return One of the {@code RESULT_} values defined in {@link Extractor}.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
public @Extractor.ReadResult int readDuration(
- ExtractorInput input, PositionHolder seekPositionHolder)
- throws IOException, InterruptedException {
+ ExtractorInput input, PositionHolder seekPositionHolder) throws IOException {
if (!isLastScrValueRead) {
return readLastScrValue(input, seekPositionHolder);
}
@@ -137,7 +135,7 @@
}
private int readFirstScrValue(ExtractorInput input, PositionHolder seekPositionHolder)
- throws IOException, InterruptedException {
+ throws IOException {
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength());
int searchStartPosition = 0;
if (input.getPosition() != searchStartPosition) {
@@ -173,7 +171,7 @@
}
private int readLastScrValue(ExtractorInput input, PositionHolder seekPositionHolder)
- throws IOException, InterruptedException {
+ throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, inputLength);
long searchStartPosition = inputLength - bytesToSearch;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java
index 7dc360b..96bdc22 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java
@@ -89,7 +89,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
byte[] scratch = new byte[14];
input.peekFully(scratch, 0, 14);
@@ -162,8 +162,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(output); // Asserts init has been called.
long inputLength = input.getLength();
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java
index 38db715..6d8cb0d 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java
@@ -52,17 +52,14 @@
"Invalid closed caption mime type provided: " + channelMimeType);
String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId();
output.format(
- Format.createTextSampleFormat(
- formatId,
- channelMimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- channelFormat.selectionFlags,
- channelFormat.language,
- channelFormat.accessibilityChannel,
- /* drmInitData= */ null,
- Format.OFFSET_SAMPLE_RELATIVE,
- channelFormat.initializationData));
+ new Format.Builder()
+ .setId(formatId)
+ .setSampleMimeType(channelMimeType)
+ .setSelectionFlags(channelFormat.selectionFlags)
+ .setLanguage(channelFormat.language)
+ .setAccessibilityChannel(channelFormat.accessibilityChannel)
+ .setInitializationData(channelFormat.initializationData)
+ .build());
outputs[i] = output;
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java
deleted file mode 100644
index e2535f1..0000000
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.extractor.ts;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.extractor.ExtractorOutput;
-import com.google.android.exoplayer2.extractor.TrackOutput;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.util.ParsableByteArray;
-import com.google.android.exoplayer2.util.TimestampAdjuster;
-import com.google.android.exoplayer2.util.Util;
-import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-
-/**
- * Parses splice info sections as defined by SCTE35.
- */
-public final class SpliceInfoSectionReader implements SectionPayloadReader {
-
- private @MonotonicNonNull TimestampAdjuster timestampAdjuster;
- private @MonotonicNonNull TrackOutput output;
- private boolean formatDeclared;
-
- @Override
- public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
- TsPayloadReader.TrackIdGenerator idGenerator) {
- this.timestampAdjuster = timestampAdjuster;
- idGenerator.generateNewId();
- output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
- output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
- null, Format.NO_VALUE, null));
- }
-
- @Override
- public void consume(ParsableByteArray sectionData) {
- assertInitialized();
- if (!formatDeclared) {
- if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
- // There is not enough information to initialize the timestamp adjuster.
- return;
- }
- output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35,
- timestampAdjuster.getTimestampOffsetUs()));
- formatDeclared = true;
- }
- int sampleSize = sectionData.bytesLeft();
- output.sampleData(sectionData, sampleSize);
- output.sampleMetadata(timestampAdjuster.getLastAdjustedTimestampUs(), C.BUFFER_FLAG_KEY_FRAME,
- sampleSize, 0, null);
- }
-
- @EnsuresNonNull({"timestampAdjuster", "output"})
- private void assertInitialized() {
- Assertions.checkStateNotNull(timestampAdjuster);
- Util.castNonNull(output);
- }
-}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java
index a627c00..8a1d2b2 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java
@@ -74,7 +74,7 @@
@Override
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
- throws IOException, InterruptedException {
+ throws IOException {
long inputPosition = input.getPosition();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java
index 804a643..a60d3fc 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java
@@ -74,11 +74,9 @@
* @param pcrPid The PID of the packet stream within this TS stream that contains PCR values.
* @return One of the {@code RESULT_} values defined in {@link Extractor}.
* @throws IOException If an error occurred reading from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
public @Extractor.ReadResult int readDuration(
- ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
- throws IOException, InterruptedException {
+ ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException {
if (pcrPid <= 0) {
return finishReadDuration(input);
}
@@ -124,7 +122,7 @@
}
private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
- throws IOException, InterruptedException {
+ throws IOException {
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength());
int searchStartPosition = 0;
if (input.getPosition() != searchStartPosition) {
@@ -159,7 +157,7 @@
}
private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
- throws IOException, InterruptedException {
+ throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, inputLength);
long searchStartPosition = inputLength - bytesToSearch;
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
index 35e8806..5e85a80 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java
@@ -96,6 +96,9 @@
public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86;
public static final int TS_STREAM_TYPE_DVBSUBS = 0x59;
+ // Stream types that aren't defined by the MPEG-2 TS specification.
+ public static final int TS_STREAM_TYPE_AIT = 0x101;
+
public static final int TS_PACKET_SIZE = 188;
public static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
@@ -187,7 +190,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
byte[] buffer = tsPacketBuffer.data;
input.peekFully(buffer, 0, TS_PACKET_SIZE * SNIFF_TS_PACKET_COUNT);
for (int startPosCandidate = 0; startPosCandidate < TS_PACKET_SIZE; startPosCandidate++) {
@@ -250,7 +253,7 @@
@Override
public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ throws IOException {
long inputLength = input.getLength();
if (tracksEnded) {
boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS;
@@ -369,8 +372,7 @@
}
}
- private boolean fillBufferWithAtLeastOnePacket(ExtractorInput input)
- throws IOException, InterruptedException {
+ private boolean fillBufferWithAtLeastOnePacket(ExtractorInput input) throws IOException {
byte[] data = tsPacketBuffer.data;
// Shift bytes to the start of the buffer if there isn't enough space left at the end.
if (BUFFER_SIZE - tsPacketBuffer.getPosition() < TS_PACKET_SIZE) {
@@ -494,6 +496,7 @@
private static final int TS_PMT_DESC_REGISTRATION = 0x05;
private static final int TS_PMT_DESC_ISO639_LANG = 0x0A;
private static final int TS_PMT_DESC_AC3 = 0x6A;
+ private static final int TS_PMT_DESC_AIT = 0x6F;
private static final int TS_PMT_DESC_EAC3 = 0x7A;
private static final int TS_PMT_DESC_DTS = 0x7B;
private static final int TS_PMT_DESC_DVB_EXT = 0x7F;
@@ -578,7 +581,7 @@
pmtScratch.skipBits(4); // reserved
int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
- if (streamType == 0x06) {
+ if (streamType == 0x06 || streamType == 0x05) {
streamType = esInfo.streamType;
}
remainingEntriesLength -= esInfoLength + 5;
@@ -688,6 +691,8 @@
dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbSubtitlingType,
initializationData));
}
+ } else if (descriptorTag == TS_PMT_DESC_AIT) {
+ streamType = TS_STREAM_TYPE_AIT;
}
// Skip unused bytes of current descriptor.
data.skipBytes(positionOfNextDescriptor - data.getPosition());
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java
index 64f9a9d..a9d1e1e 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java
@@ -51,17 +51,14 @@
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType);
output.format(
- Format.createTextSampleFormat(
- idGenerator.getFormatId(),
- channelMimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- channelFormat.selectionFlags,
- channelFormat.language,
- channelFormat.accessibilityChannel,
- /* drmInitData= */ null,
- Format.OFFSET_SAMPLE_RELATIVE,
- channelFormat.initializationData));
+ new Format.Builder()
+ .setId(idGenerator.getFormatId())
+ .setSampleMimeType(channelMimeType)
+ .setSelectionFlags(channelFormat.selectionFlags)
+ .setLanguage(channelFormat.language)
+ .setAccessibilityChannel(channelFormat.accessibilityChannel)
+ .setInitializationData(channelFormat.initializationData)
+ .build());
outputs[i] = output;
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java
index 7694bd4..1d7b6b9 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java
@@ -61,7 +61,7 @@
}
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
return WavHeaderReader.peek(input) != null;
}
@@ -85,8 +85,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
assertInitialized();
if (outputWriter == null) {
WavHeader header = WavHeaderReader.peek(input);
@@ -176,10 +175,8 @@
* @param bytesLeft The number of sample data bytes left to be read from the input.
* @return Whether the end of the sample data has been reached.
* @throws IOException If an error occurs reading from the input.
- * @throws InterruptedException If the thread has been interrupted.
*/
- boolean sampleData(ExtractorInput input, long bytesLeft)
- throws IOException, InterruptedException;
+ boolean sampleData(ExtractorInput input, long bytesLeft) throws IOException;
}
private static final class PassthroughOutputWriter implements OutputWriter {
@@ -223,22 +220,19 @@
"Expected block size: " + bytesPerFrame + "; got: " + header.blockSize);
}
+ int constantBitrate = header.frameRateHz * bytesPerFrame * 8;
targetSampleSizeBytes =
Math.max(bytesPerFrame, header.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND);
format =
- Format.createAudioSampleFormat(
- /* id= */ null,
- mimeType,
- /* codecs= */ null,
- /* bitrate= */ header.frameRateHz * bytesPerFrame * 8,
- /* maxInputSize= */ targetSampleSizeBytes,
- header.numChannels,
- header.frameRateHz,
- pcmEncoding,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(mimeType)
+ .setAverageBitrate(constantBitrate)
+ .setPeakBitrate(constantBitrate)
+ .setMaxInputSize(targetSampleSizeBytes)
+ .setChannelCount(header.numChannels)
+ .setSampleRate(header.frameRateHz)
+ .setPcmEncoding(pcmEncoding)
+ .build();
}
@Override
@@ -256,17 +250,16 @@
}
@Override
- public boolean sampleData(ExtractorInput input, long bytesLeft)
- throws IOException, InterruptedException {
+ public boolean sampleData(ExtractorInput input, long bytesLeft) throws IOException {
// Write sample data until we've reached the target sample size, or the end of the data.
- boolean endOfSampleData = bytesLeft == 0;
- while (!endOfSampleData && pendingOutputBytes < targetSampleSizeBytes) {
+ while (bytesLeft > 0 && pendingOutputBytes < targetSampleSizeBytes) {
int bytesToRead = (int) Math.min(targetSampleSizeBytes - pendingOutputBytes, bytesLeft);
int bytesAppended = trackOutput.sampleData(input, bytesToRead, true);
if (bytesAppended == RESULT_END_OF_INPUT) {
- endOfSampleData = true;
+ bytesLeft = 0;
} else {
pendingOutputBytes += bytesAppended;
+ bytesLeft -= bytesAppended;
}
}
@@ -288,7 +281,7 @@
pendingOutputBytes = offset;
}
- return endOfSampleData;
+ return bytesLeft <= 0;
}
}
@@ -371,21 +364,17 @@
// Create the format. We calculate the bitrate of the data before decoding, since this is the
// bitrate of the stream itself.
- int bitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock;
+ int constantBitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock;
format =
- Format.createAudioSampleFormat(
- /* id= */ null,
- MimeTypes.AUDIO_RAW,
- /* codecs= */ null,
- bitrate,
- /* maxInputSize= */ numOutputFramesToBytes(targetSampleSizeFrames, numChannels),
- header.numChannels,
- header.frameRateHz,
- C.ENCODING_PCM_16BIT,
- /* initializationData= */ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_RAW)
+ .setAverageBitrate(constantBitrate)
+ .setPeakBitrate(constantBitrate)
+ .setMaxInputSize(numOutputFramesToBytes(targetSampleSizeFrames, numChannels))
+ .setChannelCount(header.numChannels)
+ .setSampleRate(header.frameRateHz)
+ .setPcmEncoding(C.ENCODING_PCM_16BIT)
+ .build();
}
@Override
@@ -404,8 +393,7 @@
}
@Override
- public boolean sampleData(ExtractorInput input, long bytesLeft)
- throws IOException, InterruptedException {
+ public boolean sampleData(ExtractorInput input, long bytesLeft) throws IOException {
// Calculate the number of additional frames that we need on the output side to complete a
// sample of the target size.
int targetFramesRemaining =
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java
index b2cdda7..bcc229f 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java
@@ -38,12 +38,11 @@
* @param input Input stream to peek the WAV header from.
* @throws ParserException If the input file is an incorrect RIFF WAV.
* @throws IOException If peeking from the input fails.
- * @throws InterruptedException If interrupted while peeking from input.
* @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a
* supported WAV format.
*/
@Nullable
- public static WavHeader peek(ExtractorInput input) throws IOException, InterruptedException {
+ public static WavHeader peek(ExtractorInput input) throws IOException {
Assertions.checkNotNull(input);
// Allocate a scratch buffer large enough to store the format chunk.
@@ -108,10 +107,8 @@
* @return The byte positions at which the data starts (inclusive) and ends (exclusive).
* @throws ParserException If an error occurs parsing chunks.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from input.
*/
- public static Pair<Long, Long> skipToData(ExtractorInput input)
- throws IOException, InterruptedException {
+ public static Pair<Long, Long> skipToData(ExtractorInput input) throws IOException {
Assertions.checkNotNull(input);
// Make sure the peek position is set to the read position before we peek the first header.
@@ -174,11 +171,10 @@
* @param input Input stream to peek the chunk header from.
* @param scratch Buffer for temporary use.
* @throws IOException If peeking from the input fails.
- * @throws InterruptedException If interrupted while peeking from input.
* @return A new {@code ChunkHeader} peeked from {@code input}.
*/
public static ChunkHeader peek(ExtractorInput input, ParsableByteArray scratch)
- throws IOException, InterruptedException {
+ throws IOException {
input.peekFully(scratch.data, /* offset= */ 0, /* length= */ SIZE_IN_BYTES);
scratch.setPosition(0);
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb.amr.0.dump b/tree/library/extractor/src/test/assets/amr/sample_nb.amr.0.dump
deleted file mode 100644
index 596f85b..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb.amr.0.dump
+++ /dev/null
@@ -1,903 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2834
- sample count = 218
- sample 0:
- time = 0
- flags = 1
- data = length 13, hash 371B046C
- sample 1:
- time = 20000
- flags = 1
- data = length 13, hash CE30BF5B
- sample 2:
- time = 40000
- flags = 1
- data = length 13, hash 19A59975
- sample 3:
- time = 60000
- flags = 1
- data = length 13, hash 4879773C
- sample 4:
- time = 80000
- flags = 1
- data = length 13, hash E8F83019
- sample 5:
- time = 100000
- flags = 1
- data = length 13, hash D265CDC9
- sample 6:
- time = 120000
- flags = 1
- data = length 13, hash 91653DAA
- sample 7:
- time = 140000
- flags = 1
- data = length 13, hash C79456F6
- sample 8:
- time = 160000
- flags = 1
- data = length 13, hash CDDC4422
- sample 9:
- time = 180000
- flags = 1
- data = length 13, hash D9ED3AF1
- sample 10:
- time = 200000
- flags = 1
- data = length 13, hash BAB75A33
- sample 11:
- time = 220000
- flags = 1
- data = length 13, hash 2221B4FF
- sample 12:
- time = 240000
- flags = 1
- data = length 13, hash 96400A0B
- sample 13:
- time = 260000
- flags = 1
- data = length 13, hash 582E6FB
- sample 14:
- time = 280000
- flags = 1
- data = length 13, hash C4E878E5
- sample 15:
- time = 300000
- flags = 1
- data = length 13, hash C849A1BD
- sample 16:
- time = 320000
- flags = 1
- data = length 13, hash CFA8A9ED
- sample 17:
- time = 340000
- flags = 1
- data = length 13, hash 70CA4907
- sample 18:
- time = 360000
- flags = 1
- data = length 13, hash B47D4454
- sample 19:
- time = 380000
- flags = 1
- data = length 13, hash 282998C1
- sample 20:
- time = 400000
- flags = 1
- data = length 13, hash 3F3F7A65
- sample 21:
- time = 420000
- flags = 1
- data = length 13, hash CC2EAB58
- sample 22:
- time = 440000
- flags = 1
- data = length 13, hash 279EF712
- sample 23:
- time = 460000
- flags = 1
- data = length 13, hash AA2F4B29
- sample 24:
- time = 480000
- flags = 1
- data = length 13, hash F6F658C4
- sample 25:
- time = 500000
- flags = 1
- data = length 13, hash D7DEBD17
- sample 26:
- time = 520000
- flags = 1
- data = length 13, hash 6DAB9A17
- sample 27:
- time = 540000
- flags = 1
- data = length 13, hash 6ECE1571
- sample 28:
- time = 560000
- flags = 1
- data = length 13, hash B3D0507F
- sample 29:
- time = 580000
- flags = 1
- data = length 13, hash 21E356B9
- sample 30:
- time = 600000
- flags = 1
- data = length 13, hash 410EA12
- sample 31:
- time = 620000
- flags = 1
- data = length 13, hash 533895A8
- sample 32:
- time = 640000
- flags = 1
- data = length 13, hash C61B3E5A
- sample 33:
- time = 660000
- flags = 1
- data = length 13, hash 982170E6
- sample 34:
- time = 680000
- flags = 1
- data = length 13, hash 7A0468C5
- sample 35:
- time = 700000
- flags = 1
- data = length 13, hash 9C85EAA7
- sample 36:
- time = 720000
- flags = 1
- data = length 13, hash B6B341B6
- sample 37:
- time = 740000
- flags = 1
- data = length 13, hash 6937532E
- sample 38:
- time = 760000
- flags = 1
- data = length 13, hash 8CF2A3A0
- sample 39:
- time = 780000
- flags = 1
- data = length 13, hash D2682AC6
- sample 40:
- time = 800000
- flags = 1
- data = length 13, hash BBC5710F
- sample 41:
- time = 820000
- flags = 1
- data = length 13, hash 59080B6C
- sample 42:
- time = 840000
- flags = 1
- data = length 13, hash E4118291
- sample 43:
- time = 860000
- flags = 1
- data = length 13, hash A1E5B296
- sample 44:
- time = 880000
- flags = 1
- data = length 13, hash D7B8F95B
- sample 45:
- time = 900000
- flags = 1
- data = length 13, hash CC839BE1
- sample 46:
- time = 920000
- flags = 1
- data = length 13, hash D459DFCE
- sample 47:
- time = 940000
- flags = 1
- data = length 13, hash D6AD19EC
- sample 48:
- time = 960000
- flags = 1
- data = length 13, hash D05E373D
- sample 49:
- time = 980000
- flags = 1
- data = length 13, hash 6A4460C7
- sample 50:
- time = 1000000
- flags = 1
- data = length 13, hash C9A0D93F
- sample 51:
- time = 1020000
- flags = 1
- data = length 13, hash 3FA819E7
- sample 52:
- time = 1040000
- flags = 1
- data = length 13, hash 1D3CBDFC
- sample 53:
- time = 1060000
- flags = 1
- data = length 13, hash 8BBBB403
- sample 54:
- time = 1080000
- flags = 1
- data = length 13, hash 21B4A0F9
- sample 55:
- time = 1100000
- flags = 1
- data = length 13, hash C0F921D1
- sample 56:
- time = 1120000
- flags = 1
- data = length 13, hash 5D812AAB
- sample 57:
- time = 1140000
- flags = 1
- data = length 13, hash 50C9F3F8
- sample 58:
- time = 1160000
- flags = 1
- data = length 13, hash 5C2BB5D1
- sample 59:
- time = 1180000
- flags = 1
- data = length 13, hash 6BF9BEA5
- sample 60:
- time = 1200000
- flags = 1
- data = length 13, hash 2738C1E6
- sample 61:
- time = 1220000
- flags = 1
- data = length 13, hash 5FC288A6
- sample 62:
- time = 1240000
- flags = 1
- data = length 13, hash 7E8E442A
- sample 63:
- time = 1260000
- flags = 1
- data = length 13, hash AEAA2BBA
- sample 64:
- time = 1280000
- flags = 1
- data = length 13, hash 4E2ACD2F
- sample 65:
- time = 1300000
- flags = 1
- data = length 13, hash D6C90ACF
- sample 66:
- time = 1320000
- flags = 1
- data = length 13, hash 6FD8A944
- sample 67:
- time = 1340000
- flags = 1
- data = length 13, hash A835BBF9
- sample 68:
- time = 1360000
- flags = 1
- data = length 13, hash F7713830
- sample 69:
- time = 1380000
- flags = 1
- data = length 13, hash 3AA966E5
- sample 70:
- time = 1400000
- flags = 1
- data = length 13, hash F939E829
- sample 71:
- time = 1420000
- flags = 1
- data = length 13, hash 7676DE49
- sample 72:
- time = 1440000
- flags = 1
- data = length 13, hash 93BB890A
- sample 73:
- time = 1460000
- flags = 1
- data = length 13, hash B57DBEC8
- sample 74:
- time = 1480000
- flags = 1
- data = length 13, hash 66B0A5B6
- sample 75:
- time = 1500000
- flags = 1
- data = length 13, hash D733E0D
- sample 76:
- time = 1520000
- flags = 1
- data = length 13, hash 80941726
- sample 77:
- time = 1540000
- flags = 1
- data = length 13, hash 556ED633
- sample 78:
- time = 1560000
- flags = 1
- data = length 13, hash C5EDF4E1
- sample 79:
- time = 1580000
- flags = 1
- data = length 13, hash 6B287445
- sample 80:
- time = 1600000
- flags = 1
- data = length 13, hash DC97C4A7
- sample 81:
- time = 1620000
- flags = 1
- data = length 13, hash DA8CBDF4
- sample 82:
- time = 1640000
- flags = 1
- data = length 13, hash 6F60FF77
- sample 83:
- time = 1660000
- flags = 1
- data = length 13, hash 3EB22B96
- sample 84:
- time = 1680000
- flags = 1
- data = length 13, hash B3C31AF5
- sample 85:
- time = 1700000
- flags = 1
- data = length 13, hash 1854AA92
- sample 86:
- time = 1720000
- flags = 1
- data = length 13, hash 6488264B
- sample 87:
- time = 1740000
- flags = 1
- data = length 13, hash 4CC8C5C1
- sample 88:
- time = 1760000
- flags = 1
- data = length 13, hash 19CC7523
- sample 89:
- time = 1780000
- flags = 1
- data = length 13, hash 9BE7B928
- sample 90:
- time = 1800000
- flags = 1
- data = length 13, hash 47EC7CFD
- sample 91:
- time = 1820000
- flags = 1
- data = length 13, hash EC940120
- sample 92:
- time = 1840000
- flags = 1
- data = length 13, hash 73BDA6D0
- sample 93:
- time = 1860000
- flags = 1
- data = length 13, hash FACB3314
- sample 94:
- time = 1880000
- flags = 1
- data = length 13, hash EC61D13B
- sample 95:
- time = 1900000
- flags = 1
- data = length 13, hash B28C7B6C
- sample 96:
- time = 1920000
- flags = 1
- data = length 13, hash B1A4CECD
- sample 97:
- time = 1940000
- flags = 1
- data = length 13, hash 56D41BA6
- sample 98:
- time = 1960000
- flags = 1
- data = length 13, hash 90499F4
- sample 99:
- time = 1980000
- flags = 1
- data = length 13, hash 65D9A9D3
- sample 100:
- time = 2000000
- flags = 1
- data = length 13, hash D9004CC
- sample 101:
- time = 2020000
- flags = 1
- data = length 13, hash 4139C6ED
- sample 102:
- time = 2040000
- flags = 1
- data = length 13, hash C4F8097C
- sample 103:
- time = 2060000
- flags = 1
- data = length 13, hash 94D424FA
- sample 104:
- time = 2080000
- flags = 1
- data = length 13, hash C2C6F5FD
- sample 105:
- time = 2100000
- flags = 1
- data = length 13, hash 15719008
- sample 106:
- time = 2120000
- flags = 1
- data = length 13, hash 4F64F524
- sample 107:
- time = 2140000
- flags = 1
- data = length 13, hash F9E01C1E
- sample 108:
- time = 2160000
- flags = 1
- data = length 13, hash 74C4EE74
- sample 109:
- time = 2180000
- flags = 1
- data = length 13, hash 7EE7553D
- sample 110:
- time = 2200000
- flags = 1
- data = length 13, hash 62DE6539
- sample 111:
- time = 2220000
- flags = 1
- data = length 13, hash 7F5EC222
- sample 112:
- time = 2240000
- flags = 1
- data = length 13, hash 644067F
- sample 113:
- time = 2260000
- flags = 1
- data = length 13, hash CDF6C9DC
- sample 114:
- time = 2280000
- flags = 1
- data = length 13, hash 8B5DBC80
- sample 115:
- time = 2300000
- flags = 1
- data = length 13, hash AD4BBA03
- sample 116:
- time = 2320000
- flags = 1
- data = length 13, hash 7A76340
- sample 117:
- time = 2340000
- flags = 1
- data = length 13, hash 3610F5B0
- sample 118:
- time = 2360000
- flags = 1
- data = length 13, hash 430BC60B
- sample 119:
- time = 2380000
- flags = 1
- data = length 13, hash 99CF1CA6
- sample 120:
- time = 2400000
- flags = 1
- data = length 13, hash 1331C70B
- sample 121:
- time = 2420000
- flags = 1
- data = length 13, hash BD76E69D
- sample 122:
- time = 2440000
- flags = 1
- data = length 13, hash 5DA652AC
- sample 123:
- time = 2460000
- flags = 1
- data = length 13, hash 3B7BF6CE
- sample 124:
- time = 2480000
- flags = 1
- data = length 13, hash ABBFD143
- sample 125:
- time = 2500000
- flags = 1
- data = length 13, hash E9447166
- sample 126:
- time = 2520000
- flags = 1
- data = length 13, hash EC40068C
- sample 127:
- time = 2540000
- flags = 1
- data = length 13, hash A2869400
- sample 128:
- time = 2560000
- flags = 1
- data = length 13, hash C7E0746B
- sample 129:
- time = 2580000
- flags = 1
- data = length 13, hash 60601BB1
- sample 130:
- time = 2600000
- flags = 1
- data = length 13, hash 975AAE9B
- sample 131:
- time = 2620000
- flags = 1
- data = length 13, hash 8BBC0EB2
- sample 132:
- time = 2640000
- flags = 1
- data = length 13, hash 57FB39E5
- sample 133:
- time = 2660000
- flags = 1
- data = length 13, hash 4CDCEEDB
- sample 134:
- time = 2680000
- flags = 1
- data = length 13, hash EA16E256
- sample 135:
- time = 2700000
- flags = 1
- data = length 13, hash 287E7D9E
- sample 136:
- time = 2720000
- flags = 1
- data = length 13, hash 55AB8FB9
- sample 137:
- time = 2740000
- flags = 1
- data = length 13, hash 129890EF
- sample 138:
- time = 2760000
- flags = 1
- data = length 13, hash 90834F57
- sample 139:
- time = 2780000
- flags = 1
- data = length 13, hash 5B3228E0
- sample 140:
- time = 2800000
- flags = 1
- data = length 13, hash DD19E175
- sample 141:
- time = 2820000
- flags = 1
- data = length 13, hash EE7EA342
- sample 142:
- time = 2840000
- flags = 1
- data = length 13, hash DB3AF473
- sample 143:
- time = 2860000
- flags = 1
- data = length 13, hash 25AEC43F
- sample 144:
- time = 2880000
- flags = 1
- data = length 13, hash EE9BF97F
- sample 145:
- time = 2900000
- flags = 1
- data = length 13, hash FFFBE047
- sample 146:
- time = 2920000
- flags = 1
- data = length 13, hash BEACFCB0
- sample 147:
- time = 2940000
- flags = 1
- data = length 13, hash AEB5096C
- sample 148:
- time = 2960000
- flags = 1
- data = length 13, hash B0D381B
- sample 149:
- time = 2980000
- flags = 1
- data = length 13, hash 3D9D5122
- sample 150:
- time = 3000000
- flags = 1
- data = length 13, hash 6C1DDB95
- sample 151:
- time = 3020000
- flags = 1
- data = length 13, hash ADACADCF
- sample 152:
- time = 3040000
- flags = 1
- data = length 13, hash 159E321E
- sample 153:
- time = 3060000
- flags = 1
- data = length 13, hash B1466264
- sample 154:
- time = 3080000
- flags = 1
- data = length 13, hash 4DDF7223
- sample 155:
- time = 3100000
- flags = 1
- data = length 13, hash C9BDB82A
- sample 156:
- time = 3120000
- flags = 1
- data = length 13, hash A49B2D9D
- sample 157:
- time = 3140000
- flags = 1
- data = length 13, hash D645E7E5
- sample 158:
- time = 3160000
- flags = 1
- data = length 13, hash 1C4232DC
- sample 159:
- time = 3180000
- flags = 1
- data = length 13, hash 83078219
- sample 160:
- time = 3200000
- flags = 1
- data = length 13, hash D6D8B072
- sample 161:
- time = 3220000
- flags = 1
- data = length 13, hash 975DB40
- sample 162:
- time = 3240000
- flags = 1
- data = length 13, hash A15FDD05
- sample 163:
- time = 3260000
- flags = 1
- data = length 13, hash 4B839E41
- sample 164:
- time = 3280000
- flags = 1
- data = length 13, hash 7418F499
- sample 165:
- time = 3300000
- flags = 1
- data = length 13, hash 7A4945E4
- sample 166:
- time = 3320000
- flags = 1
- data = length 13, hash 6249558C
- sample 167:
- time = 3340000
- flags = 1
- data = length 13, hash BD4C5BE3
- sample 168:
- time = 3360000
- flags = 1
- data = length 13, hash BAB30F1D
- sample 169:
- time = 3380000
- flags = 1
- data = length 13, hash 1E1C7012
- sample 170:
- time = 3400000
- flags = 1
- data = length 13, hash 9A3F8A89
- sample 171:
- time = 3420000
- flags = 1
- data = length 13, hash 20BE6D7B
- sample 172:
- time = 3440000
- flags = 1
- data = length 13, hash CAA0591D
- sample 173:
- time = 3460000
- flags = 1
- data = length 13, hash 6D554D17
- sample 174:
- time = 3480000
- flags = 1
- data = length 13, hash D97C3B31
- sample 175:
- time = 3500000
- flags = 1
- data = length 13, hash 75BC5C3
- sample 176:
- time = 3520000
- flags = 1
- data = length 13, hash 7BA1784B
- sample 177:
- time = 3540000
- flags = 1
- data = length 13, hash 1D175D92
- sample 178:
- time = 3560000
- flags = 1
- data = length 13, hash ADCA60FD
- sample 179:
- time = 3580000
- flags = 1
- data = length 13, hash 37018693
- sample 180:
- time = 3600000
- flags = 1
- data = length 13, hash 4553606F
- sample 181:
- time = 3620000
- flags = 1
- data = length 13, hash CF434565
- sample 182:
- time = 3640000
- flags = 1
- data = length 13, hash D264D757
- sample 183:
- time = 3660000
- flags = 1
- data = length 13, hash 4FB493EF
- sample 184:
- time = 3680000
- flags = 1
- data = length 13, hash 919F53A
- sample 185:
- time = 3700000
- flags = 1
- data = length 13, hash C22B009B
- sample 186:
- time = 3720000
- flags = 1
- data = length 13, hash 5981470
- sample 187:
- time = 3740000
- flags = 1
- data = length 13, hash A5D3937C
- sample 188:
- time = 3760000
- flags = 1
- data = length 13, hash A2504429
- sample 189:
- time = 3780000
- flags = 1
- data = length 13, hash AD1B70BE
- sample 190:
- time = 3800000
- flags = 1
- data = length 13, hash 2E39ED5E
- sample 191:
- time = 3820000
- flags = 1
- data = length 13, hash 13A8BE8E
- sample 192:
- time = 3840000
- flags = 1
- data = length 13, hash 1ACD740B
- sample 193:
- time = 3860000
- flags = 1
- data = length 13, hash 80F38B3
- sample 194:
- time = 3880000
- flags = 1
- data = length 13, hash DA9DA79F
- sample 195:
- time = 3900000
- flags = 1
- data = length 13, hash 21B95B7E
- sample 196:
- time = 3920000
- flags = 1
- data = length 13, hash CD22497B
- sample 197:
- time = 3940000
- flags = 1
- data = length 13, hash 718BB35D
- sample 198:
- time = 3960000
- flags = 1
- data = length 13, hash 69ABA6AD
- sample 199:
- time = 3980000
- flags = 1
- data = length 13, hash BAE19549
- sample 200:
- time = 4000000
- flags = 1
- data = length 13, hash 2A792FB3
- sample 201:
- time = 4020000
- flags = 1
- data = length 13, hash 71FCD8
- sample 202:
- time = 4040000
- flags = 1
- data = length 13, hash 44D2B5B3
- sample 203:
- time = 4060000
- flags = 1
- data = length 13, hash 1E87B11B
- sample 204:
- time = 4080000
- flags = 1
- data = length 13, hash 78CD2C11
- sample 205:
- time = 4100000
- flags = 1
- data = length 13, hash 9F198DF0
- sample 206:
- time = 4120000
- flags = 1
- data = length 13, hash B291F16A
- sample 207:
- time = 4140000
- flags = 1
- data = length 13, hash CF820EE0
- sample 208:
- time = 4160000
- flags = 1
- data = length 13, hash 4E24F683
- sample 209:
- time = 4180000
- flags = 1
- data = length 13, hash 52BCD68F
- sample 210:
- time = 4200000
- flags = 1
- data = length 13, hash 42588CB0
- sample 211:
- time = 4220000
- flags = 1
- data = length 13, hash EBBFECA2
- sample 212:
- time = 4240000
- flags = 1
- data = length 13, hash C11050CF
- sample 213:
- time = 4260000
- flags = 1
- data = length 13, hash 6F738603
- sample 214:
- time = 4280000
- flags = 1
- data = length 13, hash DAD06E5
- sample 215:
- time = 4300000
- flags = 1
- data = length 13, hash 5B036C64
- sample 216:
- time = 4320000
- flags = 1
- data = length 13, hash A58DC12E
- sample 217:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.0.dump b/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.0.dump
deleted file mode 100644
index 40d99d3..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.0.dump
+++ /dev/null
@@ -1,903 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 4360000
- getPosition(0) = [[timeUs=0, position=6]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2834
- sample count = 218
- sample 0:
- time = 0
- flags = 1
- data = length 13, hash 371B046C
- sample 1:
- time = 20000
- flags = 1
- data = length 13, hash CE30BF5B
- sample 2:
- time = 40000
- flags = 1
- data = length 13, hash 19A59975
- sample 3:
- time = 60000
- flags = 1
- data = length 13, hash 4879773C
- sample 4:
- time = 80000
- flags = 1
- data = length 13, hash E8F83019
- sample 5:
- time = 100000
- flags = 1
- data = length 13, hash D265CDC9
- sample 6:
- time = 120000
- flags = 1
- data = length 13, hash 91653DAA
- sample 7:
- time = 140000
- flags = 1
- data = length 13, hash C79456F6
- sample 8:
- time = 160000
- flags = 1
- data = length 13, hash CDDC4422
- sample 9:
- time = 180000
- flags = 1
- data = length 13, hash D9ED3AF1
- sample 10:
- time = 200000
- flags = 1
- data = length 13, hash BAB75A33
- sample 11:
- time = 220000
- flags = 1
- data = length 13, hash 2221B4FF
- sample 12:
- time = 240000
- flags = 1
- data = length 13, hash 96400A0B
- sample 13:
- time = 260000
- flags = 1
- data = length 13, hash 582E6FB
- sample 14:
- time = 280000
- flags = 1
- data = length 13, hash C4E878E5
- sample 15:
- time = 300000
- flags = 1
- data = length 13, hash C849A1BD
- sample 16:
- time = 320000
- flags = 1
- data = length 13, hash CFA8A9ED
- sample 17:
- time = 340000
- flags = 1
- data = length 13, hash 70CA4907
- sample 18:
- time = 360000
- flags = 1
- data = length 13, hash B47D4454
- sample 19:
- time = 380000
- flags = 1
- data = length 13, hash 282998C1
- sample 20:
- time = 400000
- flags = 1
- data = length 13, hash 3F3F7A65
- sample 21:
- time = 420000
- flags = 1
- data = length 13, hash CC2EAB58
- sample 22:
- time = 440000
- flags = 1
- data = length 13, hash 279EF712
- sample 23:
- time = 460000
- flags = 1
- data = length 13, hash AA2F4B29
- sample 24:
- time = 480000
- flags = 1
- data = length 13, hash F6F658C4
- sample 25:
- time = 500000
- flags = 1
- data = length 13, hash D7DEBD17
- sample 26:
- time = 520000
- flags = 1
- data = length 13, hash 6DAB9A17
- sample 27:
- time = 540000
- flags = 1
- data = length 13, hash 6ECE1571
- sample 28:
- time = 560000
- flags = 1
- data = length 13, hash B3D0507F
- sample 29:
- time = 580000
- flags = 1
- data = length 13, hash 21E356B9
- sample 30:
- time = 600000
- flags = 1
- data = length 13, hash 410EA12
- sample 31:
- time = 620000
- flags = 1
- data = length 13, hash 533895A8
- sample 32:
- time = 640000
- flags = 1
- data = length 13, hash C61B3E5A
- sample 33:
- time = 660000
- flags = 1
- data = length 13, hash 982170E6
- sample 34:
- time = 680000
- flags = 1
- data = length 13, hash 7A0468C5
- sample 35:
- time = 700000
- flags = 1
- data = length 13, hash 9C85EAA7
- sample 36:
- time = 720000
- flags = 1
- data = length 13, hash B6B341B6
- sample 37:
- time = 740000
- flags = 1
- data = length 13, hash 6937532E
- sample 38:
- time = 760000
- flags = 1
- data = length 13, hash 8CF2A3A0
- sample 39:
- time = 780000
- flags = 1
- data = length 13, hash D2682AC6
- sample 40:
- time = 800000
- flags = 1
- data = length 13, hash BBC5710F
- sample 41:
- time = 820000
- flags = 1
- data = length 13, hash 59080B6C
- sample 42:
- time = 840000
- flags = 1
- data = length 13, hash E4118291
- sample 43:
- time = 860000
- flags = 1
- data = length 13, hash A1E5B296
- sample 44:
- time = 880000
- flags = 1
- data = length 13, hash D7B8F95B
- sample 45:
- time = 900000
- flags = 1
- data = length 13, hash CC839BE1
- sample 46:
- time = 920000
- flags = 1
- data = length 13, hash D459DFCE
- sample 47:
- time = 940000
- flags = 1
- data = length 13, hash D6AD19EC
- sample 48:
- time = 960000
- flags = 1
- data = length 13, hash D05E373D
- sample 49:
- time = 980000
- flags = 1
- data = length 13, hash 6A4460C7
- sample 50:
- time = 1000000
- flags = 1
- data = length 13, hash C9A0D93F
- sample 51:
- time = 1020000
- flags = 1
- data = length 13, hash 3FA819E7
- sample 52:
- time = 1040000
- flags = 1
- data = length 13, hash 1D3CBDFC
- sample 53:
- time = 1060000
- flags = 1
- data = length 13, hash 8BBBB403
- sample 54:
- time = 1080000
- flags = 1
- data = length 13, hash 21B4A0F9
- sample 55:
- time = 1100000
- flags = 1
- data = length 13, hash C0F921D1
- sample 56:
- time = 1120000
- flags = 1
- data = length 13, hash 5D812AAB
- sample 57:
- time = 1140000
- flags = 1
- data = length 13, hash 50C9F3F8
- sample 58:
- time = 1160000
- flags = 1
- data = length 13, hash 5C2BB5D1
- sample 59:
- time = 1180000
- flags = 1
- data = length 13, hash 6BF9BEA5
- sample 60:
- time = 1200000
- flags = 1
- data = length 13, hash 2738C1E6
- sample 61:
- time = 1220000
- flags = 1
- data = length 13, hash 5FC288A6
- sample 62:
- time = 1240000
- flags = 1
- data = length 13, hash 7E8E442A
- sample 63:
- time = 1260000
- flags = 1
- data = length 13, hash AEAA2BBA
- sample 64:
- time = 1280000
- flags = 1
- data = length 13, hash 4E2ACD2F
- sample 65:
- time = 1300000
- flags = 1
- data = length 13, hash D6C90ACF
- sample 66:
- time = 1320000
- flags = 1
- data = length 13, hash 6FD8A944
- sample 67:
- time = 1340000
- flags = 1
- data = length 13, hash A835BBF9
- sample 68:
- time = 1360000
- flags = 1
- data = length 13, hash F7713830
- sample 69:
- time = 1380000
- flags = 1
- data = length 13, hash 3AA966E5
- sample 70:
- time = 1400000
- flags = 1
- data = length 13, hash F939E829
- sample 71:
- time = 1420000
- flags = 1
- data = length 13, hash 7676DE49
- sample 72:
- time = 1440000
- flags = 1
- data = length 13, hash 93BB890A
- sample 73:
- time = 1460000
- flags = 1
- data = length 13, hash B57DBEC8
- sample 74:
- time = 1480000
- flags = 1
- data = length 13, hash 66B0A5B6
- sample 75:
- time = 1500000
- flags = 1
- data = length 13, hash D733E0D
- sample 76:
- time = 1520000
- flags = 1
- data = length 13, hash 80941726
- sample 77:
- time = 1540000
- flags = 1
- data = length 13, hash 556ED633
- sample 78:
- time = 1560000
- flags = 1
- data = length 13, hash C5EDF4E1
- sample 79:
- time = 1580000
- flags = 1
- data = length 13, hash 6B287445
- sample 80:
- time = 1600000
- flags = 1
- data = length 13, hash DC97C4A7
- sample 81:
- time = 1620000
- flags = 1
- data = length 13, hash DA8CBDF4
- sample 82:
- time = 1640000
- flags = 1
- data = length 13, hash 6F60FF77
- sample 83:
- time = 1660000
- flags = 1
- data = length 13, hash 3EB22B96
- sample 84:
- time = 1680000
- flags = 1
- data = length 13, hash B3C31AF5
- sample 85:
- time = 1700000
- flags = 1
- data = length 13, hash 1854AA92
- sample 86:
- time = 1720000
- flags = 1
- data = length 13, hash 6488264B
- sample 87:
- time = 1740000
- flags = 1
- data = length 13, hash 4CC8C5C1
- sample 88:
- time = 1760000
- flags = 1
- data = length 13, hash 19CC7523
- sample 89:
- time = 1780000
- flags = 1
- data = length 13, hash 9BE7B928
- sample 90:
- time = 1800000
- flags = 1
- data = length 13, hash 47EC7CFD
- sample 91:
- time = 1820000
- flags = 1
- data = length 13, hash EC940120
- sample 92:
- time = 1840000
- flags = 1
- data = length 13, hash 73BDA6D0
- sample 93:
- time = 1860000
- flags = 1
- data = length 13, hash FACB3314
- sample 94:
- time = 1880000
- flags = 1
- data = length 13, hash EC61D13B
- sample 95:
- time = 1900000
- flags = 1
- data = length 13, hash B28C7B6C
- sample 96:
- time = 1920000
- flags = 1
- data = length 13, hash B1A4CECD
- sample 97:
- time = 1940000
- flags = 1
- data = length 13, hash 56D41BA6
- sample 98:
- time = 1960000
- flags = 1
- data = length 13, hash 90499F4
- sample 99:
- time = 1980000
- flags = 1
- data = length 13, hash 65D9A9D3
- sample 100:
- time = 2000000
- flags = 1
- data = length 13, hash D9004CC
- sample 101:
- time = 2020000
- flags = 1
- data = length 13, hash 4139C6ED
- sample 102:
- time = 2040000
- flags = 1
- data = length 13, hash C4F8097C
- sample 103:
- time = 2060000
- flags = 1
- data = length 13, hash 94D424FA
- sample 104:
- time = 2080000
- flags = 1
- data = length 13, hash C2C6F5FD
- sample 105:
- time = 2100000
- flags = 1
- data = length 13, hash 15719008
- sample 106:
- time = 2120000
- flags = 1
- data = length 13, hash 4F64F524
- sample 107:
- time = 2140000
- flags = 1
- data = length 13, hash F9E01C1E
- sample 108:
- time = 2160000
- flags = 1
- data = length 13, hash 74C4EE74
- sample 109:
- time = 2180000
- flags = 1
- data = length 13, hash 7EE7553D
- sample 110:
- time = 2200000
- flags = 1
- data = length 13, hash 62DE6539
- sample 111:
- time = 2220000
- flags = 1
- data = length 13, hash 7F5EC222
- sample 112:
- time = 2240000
- flags = 1
- data = length 13, hash 644067F
- sample 113:
- time = 2260000
- flags = 1
- data = length 13, hash CDF6C9DC
- sample 114:
- time = 2280000
- flags = 1
- data = length 13, hash 8B5DBC80
- sample 115:
- time = 2300000
- flags = 1
- data = length 13, hash AD4BBA03
- sample 116:
- time = 2320000
- flags = 1
- data = length 13, hash 7A76340
- sample 117:
- time = 2340000
- flags = 1
- data = length 13, hash 3610F5B0
- sample 118:
- time = 2360000
- flags = 1
- data = length 13, hash 430BC60B
- sample 119:
- time = 2380000
- flags = 1
- data = length 13, hash 99CF1CA6
- sample 120:
- time = 2400000
- flags = 1
- data = length 13, hash 1331C70B
- sample 121:
- time = 2420000
- flags = 1
- data = length 13, hash BD76E69D
- sample 122:
- time = 2440000
- flags = 1
- data = length 13, hash 5DA652AC
- sample 123:
- time = 2460000
- flags = 1
- data = length 13, hash 3B7BF6CE
- sample 124:
- time = 2480000
- flags = 1
- data = length 13, hash ABBFD143
- sample 125:
- time = 2500000
- flags = 1
- data = length 13, hash E9447166
- sample 126:
- time = 2520000
- flags = 1
- data = length 13, hash EC40068C
- sample 127:
- time = 2540000
- flags = 1
- data = length 13, hash A2869400
- sample 128:
- time = 2560000
- flags = 1
- data = length 13, hash C7E0746B
- sample 129:
- time = 2580000
- flags = 1
- data = length 13, hash 60601BB1
- sample 130:
- time = 2600000
- flags = 1
- data = length 13, hash 975AAE9B
- sample 131:
- time = 2620000
- flags = 1
- data = length 13, hash 8BBC0EB2
- sample 132:
- time = 2640000
- flags = 1
- data = length 13, hash 57FB39E5
- sample 133:
- time = 2660000
- flags = 1
- data = length 13, hash 4CDCEEDB
- sample 134:
- time = 2680000
- flags = 1
- data = length 13, hash EA16E256
- sample 135:
- time = 2700000
- flags = 1
- data = length 13, hash 287E7D9E
- sample 136:
- time = 2720000
- flags = 1
- data = length 13, hash 55AB8FB9
- sample 137:
- time = 2740000
- flags = 1
- data = length 13, hash 129890EF
- sample 138:
- time = 2760000
- flags = 1
- data = length 13, hash 90834F57
- sample 139:
- time = 2780000
- flags = 1
- data = length 13, hash 5B3228E0
- sample 140:
- time = 2800000
- flags = 1
- data = length 13, hash DD19E175
- sample 141:
- time = 2820000
- flags = 1
- data = length 13, hash EE7EA342
- sample 142:
- time = 2840000
- flags = 1
- data = length 13, hash DB3AF473
- sample 143:
- time = 2860000
- flags = 1
- data = length 13, hash 25AEC43F
- sample 144:
- time = 2880000
- flags = 1
- data = length 13, hash EE9BF97F
- sample 145:
- time = 2900000
- flags = 1
- data = length 13, hash FFFBE047
- sample 146:
- time = 2920000
- flags = 1
- data = length 13, hash BEACFCB0
- sample 147:
- time = 2940000
- flags = 1
- data = length 13, hash AEB5096C
- sample 148:
- time = 2960000
- flags = 1
- data = length 13, hash B0D381B
- sample 149:
- time = 2980000
- flags = 1
- data = length 13, hash 3D9D5122
- sample 150:
- time = 3000000
- flags = 1
- data = length 13, hash 6C1DDB95
- sample 151:
- time = 3020000
- flags = 1
- data = length 13, hash ADACADCF
- sample 152:
- time = 3040000
- flags = 1
- data = length 13, hash 159E321E
- sample 153:
- time = 3060000
- flags = 1
- data = length 13, hash B1466264
- sample 154:
- time = 3080000
- flags = 1
- data = length 13, hash 4DDF7223
- sample 155:
- time = 3100000
- flags = 1
- data = length 13, hash C9BDB82A
- sample 156:
- time = 3120000
- flags = 1
- data = length 13, hash A49B2D9D
- sample 157:
- time = 3140000
- flags = 1
- data = length 13, hash D645E7E5
- sample 158:
- time = 3160000
- flags = 1
- data = length 13, hash 1C4232DC
- sample 159:
- time = 3180000
- flags = 1
- data = length 13, hash 83078219
- sample 160:
- time = 3200000
- flags = 1
- data = length 13, hash D6D8B072
- sample 161:
- time = 3220000
- flags = 1
- data = length 13, hash 975DB40
- sample 162:
- time = 3240000
- flags = 1
- data = length 13, hash A15FDD05
- sample 163:
- time = 3260000
- flags = 1
- data = length 13, hash 4B839E41
- sample 164:
- time = 3280000
- flags = 1
- data = length 13, hash 7418F499
- sample 165:
- time = 3300000
- flags = 1
- data = length 13, hash 7A4945E4
- sample 166:
- time = 3320000
- flags = 1
- data = length 13, hash 6249558C
- sample 167:
- time = 3340000
- flags = 1
- data = length 13, hash BD4C5BE3
- sample 168:
- time = 3360000
- flags = 1
- data = length 13, hash BAB30F1D
- sample 169:
- time = 3380000
- flags = 1
- data = length 13, hash 1E1C7012
- sample 170:
- time = 3400000
- flags = 1
- data = length 13, hash 9A3F8A89
- sample 171:
- time = 3420000
- flags = 1
- data = length 13, hash 20BE6D7B
- sample 172:
- time = 3440000
- flags = 1
- data = length 13, hash CAA0591D
- sample 173:
- time = 3460000
- flags = 1
- data = length 13, hash 6D554D17
- sample 174:
- time = 3480000
- flags = 1
- data = length 13, hash D97C3B31
- sample 175:
- time = 3500000
- flags = 1
- data = length 13, hash 75BC5C3
- sample 176:
- time = 3520000
- flags = 1
- data = length 13, hash 7BA1784B
- sample 177:
- time = 3540000
- flags = 1
- data = length 13, hash 1D175D92
- sample 178:
- time = 3560000
- flags = 1
- data = length 13, hash ADCA60FD
- sample 179:
- time = 3580000
- flags = 1
- data = length 13, hash 37018693
- sample 180:
- time = 3600000
- flags = 1
- data = length 13, hash 4553606F
- sample 181:
- time = 3620000
- flags = 1
- data = length 13, hash CF434565
- sample 182:
- time = 3640000
- flags = 1
- data = length 13, hash D264D757
- sample 183:
- time = 3660000
- flags = 1
- data = length 13, hash 4FB493EF
- sample 184:
- time = 3680000
- flags = 1
- data = length 13, hash 919F53A
- sample 185:
- time = 3700000
- flags = 1
- data = length 13, hash C22B009B
- sample 186:
- time = 3720000
- flags = 1
- data = length 13, hash 5981470
- sample 187:
- time = 3740000
- flags = 1
- data = length 13, hash A5D3937C
- sample 188:
- time = 3760000
- flags = 1
- data = length 13, hash A2504429
- sample 189:
- time = 3780000
- flags = 1
- data = length 13, hash AD1B70BE
- sample 190:
- time = 3800000
- flags = 1
- data = length 13, hash 2E39ED5E
- sample 191:
- time = 3820000
- flags = 1
- data = length 13, hash 13A8BE8E
- sample 192:
- time = 3840000
- flags = 1
- data = length 13, hash 1ACD740B
- sample 193:
- time = 3860000
- flags = 1
- data = length 13, hash 80F38B3
- sample 194:
- time = 3880000
- flags = 1
- data = length 13, hash DA9DA79F
- sample 195:
- time = 3900000
- flags = 1
- data = length 13, hash 21B95B7E
- sample 196:
- time = 3920000
- flags = 1
- data = length 13, hash CD22497B
- sample 197:
- time = 3940000
- flags = 1
- data = length 13, hash 718BB35D
- sample 198:
- time = 3960000
- flags = 1
- data = length 13, hash 69ABA6AD
- sample 199:
- time = 3980000
- flags = 1
- data = length 13, hash BAE19549
- sample 200:
- time = 4000000
- flags = 1
- data = length 13, hash 2A792FB3
- sample 201:
- time = 4020000
- flags = 1
- data = length 13, hash 71FCD8
- sample 202:
- time = 4040000
- flags = 1
- data = length 13, hash 44D2B5B3
- sample 203:
- time = 4060000
- flags = 1
- data = length 13, hash 1E87B11B
- sample 204:
- time = 4080000
- flags = 1
- data = length 13, hash 78CD2C11
- sample 205:
- time = 4100000
- flags = 1
- data = length 13, hash 9F198DF0
- sample 206:
- time = 4120000
- flags = 1
- data = length 13, hash B291F16A
- sample 207:
- time = 4140000
- flags = 1
- data = length 13, hash CF820EE0
- sample 208:
- time = 4160000
- flags = 1
- data = length 13, hash 4E24F683
- sample 209:
- time = 4180000
- flags = 1
- data = length 13, hash 52BCD68F
- sample 210:
- time = 4200000
- flags = 1
- data = length 13, hash 42588CB0
- sample 211:
- time = 4220000
- flags = 1
- data = length 13, hash EBBFECA2
- sample 212:
- time = 4240000
- flags = 1
- data = length 13, hash C11050CF
- sample 213:
- time = 4260000
- flags = 1
- data = length 13, hash 6F738603
- sample 214:
- time = 4280000
- flags = 1
- data = length 13, hash DAD06E5
- sample 215:
- time = 4300000
- flags = 1
- data = length 13, hash 5B036C64
- sample 216:
- time = 4320000
- flags = 1
- data = length 13, hash A58DC12E
- sample 217:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.1.dump b/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.1.dump
deleted file mode 100644
index 774593c..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.1.dump
+++ /dev/null
@@ -1,615 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 4360000
- getPosition(0) = [[timeUs=0, position=6]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 1898
- sample count = 146
- sample 0:
- time = 1440000
- flags = 1
- data = length 13, hash 93BB890A
- sample 1:
- time = 1460000
- flags = 1
- data = length 13, hash B57DBEC8
- sample 2:
- time = 1480000
- flags = 1
- data = length 13, hash 66B0A5B6
- sample 3:
- time = 1500000
- flags = 1
- data = length 13, hash D733E0D
- sample 4:
- time = 1520000
- flags = 1
- data = length 13, hash 80941726
- sample 5:
- time = 1540000
- flags = 1
- data = length 13, hash 556ED633
- sample 6:
- time = 1560000
- flags = 1
- data = length 13, hash C5EDF4E1
- sample 7:
- time = 1580000
- flags = 1
- data = length 13, hash 6B287445
- sample 8:
- time = 1600000
- flags = 1
- data = length 13, hash DC97C4A7
- sample 9:
- time = 1620000
- flags = 1
- data = length 13, hash DA8CBDF4
- sample 10:
- time = 1640000
- flags = 1
- data = length 13, hash 6F60FF77
- sample 11:
- time = 1660000
- flags = 1
- data = length 13, hash 3EB22B96
- sample 12:
- time = 1680000
- flags = 1
- data = length 13, hash B3C31AF5
- sample 13:
- time = 1700000
- flags = 1
- data = length 13, hash 1854AA92
- sample 14:
- time = 1720000
- flags = 1
- data = length 13, hash 6488264B
- sample 15:
- time = 1740000
- flags = 1
- data = length 13, hash 4CC8C5C1
- sample 16:
- time = 1760000
- flags = 1
- data = length 13, hash 19CC7523
- sample 17:
- time = 1780000
- flags = 1
- data = length 13, hash 9BE7B928
- sample 18:
- time = 1800000
- flags = 1
- data = length 13, hash 47EC7CFD
- sample 19:
- time = 1820000
- flags = 1
- data = length 13, hash EC940120
- sample 20:
- time = 1840000
- flags = 1
- data = length 13, hash 73BDA6D0
- sample 21:
- time = 1860000
- flags = 1
- data = length 13, hash FACB3314
- sample 22:
- time = 1880000
- flags = 1
- data = length 13, hash EC61D13B
- sample 23:
- time = 1900000
- flags = 1
- data = length 13, hash B28C7B6C
- sample 24:
- time = 1920000
- flags = 1
- data = length 13, hash B1A4CECD
- sample 25:
- time = 1940000
- flags = 1
- data = length 13, hash 56D41BA6
- sample 26:
- time = 1960000
- flags = 1
- data = length 13, hash 90499F4
- sample 27:
- time = 1980000
- flags = 1
- data = length 13, hash 65D9A9D3
- sample 28:
- time = 2000000
- flags = 1
- data = length 13, hash D9004CC
- sample 29:
- time = 2020000
- flags = 1
- data = length 13, hash 4139C6ED
- sample 30:
- time = 2040000
- flags = 1
- data = length 13, hash C4F8097C
- sample 31:
- time = 2060000
- flags = 1
- data = length 13, hash 94D424FA
- sample 32:
- time = 2080000
- flags = 1
- data = length 13, hash C2C6F5FD
- sample 33:
- time = 2100000
- flags = 1
- data = length 13, hash 15719008
- sample 34:
- time = 2120000
- flags = 1
- data = length 13, hash 4F64F524
- sample 35:
- time = 2140000
- flags = 1
- data = length 13, hash F9E01C1E
- sample 36:
- time = 2160000
- flags = 1
- data = length 13, hash 74C4EE74
- sample 37:
- time = 2180000
- flags = 1
- data = length 13, hash 7EE7553D
- sample 38:
- time = 2200000
- flags = 1
- data = length 13, hash 62DE6539
- sample 39:
- time = 2220000
- flags = 1
- data = length 13, hash 7F5EC222
- sample 40:
- time = 2240000
- flags = 1
- data = length 13, hash 644067F
- sample 41:
- time = 2260000
- flags = 1
- data = length 13, hash CDF6C9DC
- sample 42:
- time = 2280000
- flags = 1
- data = length 13, hash 8B5DBC80
- sample 43:
- time = 2300000
- flags = 1
- data = length 13, hash AD4BBA03
- sample 44:
- time = 2320000
- flags = 1
- data = length 13, hash 7A76340
- sample 45:
- time = 2340000
- flags = 1
- data = length 13, hash 3610F5B0
- sample 46:
- time = 2360000
- flags = 1
- data = length 13, hash 430BC60B
- sample 47:
- time = 2380000
- flags = 1
- data = length 13, hash 99CF1CA6
- sample 48:
- time = 2400000
- flags = 1
- data = length 13, hash 1331C70B
- sample 49:
- time = 2420000
- flags = 1
- data = length 13, hash BD76E69D
- sample 50:
- time = 2440000
- flags = 1
- data = length 13, hash 5DA652AC
- sample 51:
- time = 2460000
- flags = 1
- data = length 13, hash 3B7BF6CE
- sample 52:
- time = 2480000
- flags = 1
- data = length 13, hash ABBFD143
- sample 53:
- time = 2500000
- flags = 1
- data = length 13, hash E9447166
- sample 54:
- time = 2520000
- flags = 1
- data = length 13, hash EC40068C
- sample 55:
- time = 2540000
- flags = 1
- data = length 13, hash A2869400
- sample 56:
- time = 2560000
- flags = 1
- data = length 13, hash C7E0746B
- sample 57:
- time = 2580000
- flags = 1
- data = length 13, hash 60601BB1
- sample 58:
- time = 2600000
- flags = 1
- data = length 13, hash 975AAE9B
- sample 59:
- time = 2620000
- flags = 1
- data = length 13, hash 8BBC0EB2
- sample 60:
- time = 2640000
- flags = 1
- data = length 13, hash 57FB39E5
- sample 61:
- time = 2660000
- flags = 1
- data = length 13, hash 4CDCEEDB
- sample 62:
- time = 2680000
- flags = 1
- data = length 13, hash EA16E256
- sample 63:
- time = 2700000
- flags = 1
- data = length 13, hash 287E7D9E
- sample 64:
- time = 2720000
- flags = 1
- data = length 13, hash 55AB8FB9
- sample 65:
- time = 2740000
- flags = 1
- data = length 13, hash 129890EF
- sample 66:
- time = 2760000
- flags = 1
- data = length 13, hash 90834F57
- sample 67:
- time = 2780000
- flags = 1
- data = length 13, hash 5B3228E0
- sample 68:
- time = 2800000
- flags = 1
- data = length 13, hash DD19E175
- sample 69:
- time = 2820000
- flags = 1
- data = length 13, hash EE7EA342
- sample 70:
- time = 2840000
- flags = 1
- data = length 13, hash DB3AF473
- sample 71:
- time = 2860000
- flags = 1
- data = length 13, hash 25AEC43F
- sample 72:
- time = 2880000
- flags = 1
- data = length 13, hash EE9BF97F
- sample 73:
- time = 2900000
- flags = 1
- data = length 13, hash FFFBE047
- sample 74:
- time = 2920000
- flags = 1
- data = length 13, hash BEACFCB0
- sample 75:
- time = 2940000
- flags = 1
- data = length 13, hash AEB5096C
- sample 76:
- time = 2960000
- flags = 1
- data = length 13, hash B0D381B
- sample 77:
- time = 2980000
- flags = 1
- data = length 13, hash 3D9D5122
- sample 78:
- time = 3000000
- flags = 1
- data = length 13, hash 6C1DDB95
- sample 79:
- time = 3020000
- flags = 1
- data = length 13, hash ADACADCF
- sample 80:
- time = 3040000
- flags = 1
- data = length 13, hash 159E321E
- sample 81:
- time = 3060000
- flags = 1
- data = length 13, hash B1466264
- sample 82:
- time = 3080000
- flags = 1
- data = length 13, hash 4DDF7223
- sample 83:
- time = 3100000
- flags = 1
- data = length 13, hash C9BDB82A
- sample 84:
- time = 3120000
- flags = 1
- data = length 13, hash A49B2D9D
- sample 85:
- time = 3140000
- flags = 1
- data = length 13, hash D645E7E5
- sample 86:
- time = 3160000
- flags = 1
- data = length 13, hash 1C4232DC
- sample 87:
- time = 3180000
- flags = 1
- data = length 13, hash 83078219
- sample 88:
- time = 3200000
- flags = 1
- data = length 13, hash D6D8B072
- sample 89:
- time = 3220000
- flags = 1
- data = length 13, hash 975DB40
- sample 90:
- time = 3240000
- flags = 1
- data = length 13, hash A15FDD05
- sample 91:
- time = 3260000
- flags = 1
- data = length 13, hash 4B839E41
- sample 92:
- time = 3280000
- flags = 1
- data = length 13, hash 7418F499
- sample 93:
- time = 3300000
- flags = 1
- data = length 13, hash 7A4945E4
- sample 94:
- time = 3320000
- flags = 1
- data = length 13, hash 6249558C
- sample 95:
- time = 3340000
- flags = 1
- data = length 13, hash BD4C5BE3
- sample 96:
- time = 3360000
- flags = 1
- data = length 13, hash BAB30F1D
- sample 97:
- time = 3380000
- flags = 1
- data = length 13, hash 1E1C7012
- sample 98:
- time = 3400000
- flags = 1
- data = length 13, hash 9A3F8A89
- sample 99:
- time = 3420000
- flags = 1
- data = length 13, hash 20BE6D7B
- sample 100:
- time = 3440000
- flags = 1
- data = length 13, hash CAA0591D
- sample 101:
- time = 3460000
- flags = 1
- data = length 13, hash 6D554D17
- sample 102:
- time = 3480000
- flags = 1
- data = length 13, hash D97C3B31
- sample 103:
- time = 3500000
- flags = 1
- data = length 13, hash 75BC5C3
- sample 104:
- time = 3520000
- flags = 1
- data = length 13, hash 7BA1784B
- sample 105:
- time = 3540000
- flags = 1
- data = length 13, hash 1D175D92
- sample 106:
- time = 3560000
- flags = 1
- data = length 13, hash ADCA60FD
- sample 107:
- time = 3580000
- flags = 1
- data = length 13, hash 37018693
- sample 108:
- time = 3600000
- flags = 1
- data = length 13, hash 4553606F
- sample 109:
- time = 3620000
- flags = 1
- data = length 13, hash CF434565
- sample 110:
- time = 3640000
- flags = 1
- data = length 13, hash D264D757
- sample 111:
- time = 3660000
- flags = 1
- data = length 13, hash 4FB493EF
- sample 112:
- time = 3680000
- flags = 1
- data = length 13, hash 919F53A
- sample 113:
- time = 3700000
- flags = 1
- data = length 13, hash C22B009B
- sample 114:
- time = 3720000
- flags = 1
- data = length 13, hash 5981470
- sample 115:
- time = 3740000
- flags = 1
- data = length 13, hash A5D3937C
- sample 116:
- time = 3760000
- flags = 1
- data = length 13, hash A2504429
- sample 117:
- time = 3780000
- flags = 1
- data = length 13, hash AD1B70BE
- sample 118:
- time = 3800000
- flags = 1
- data = length 13, hash 2E39ED5E
- sample 119:
- time = 3820000
- flags = 1
- data = length 13, hash 13A8BE8E
- sample 120:
- time = 3840000
- flags = 1
- data = length 13, hash 1ACD740B
- sample 121:
- time = 3860000
- flags = 1
- data = length 13, hash 80F38B3
- sample 122:
- time = 3880000
- flags = 1
- data = length 13, hash DA9DA79F
- sample 123:
- time = 3900000
- flags = 1
- data = length 13, hash 21B95B7E
- sample 124:
- time = 3920000
- flags = 1
- data = length 13, hash CD22497B
- sample 125:
- time = 3940000
- flags = 1
- data = length 13, hash 718BB35D
- sample 126:
- time = 3960000
- flags = 1
- data = length 13, hash 69ABA6AD
- sample 127:
- time = 3980000
- flags = 1
- data = length 13, hash BAE19549
- sample 128:
- time = 4000000
- flags = 1
- data = length 13, hash 2A792FB3
- sample 129:
- time = 4020000
- flags = 1
- data = length 13, hash 71FCD8
- sample 130:
- time = 4040000
- flags = 1
- data = length 13, hash 44D2B5B3
- sample 131:
- time = 4060000
- flags = 1
- data = length 13, hash 1E87B11B
- sample 132:
- time = 4080000
- flags = 1
- data = length 13, hash 78CD2C11
- sample 133:
- time = 4100000
- flags = 1
- data = length 13, hash 9F198DF0
- sample 134:
- time = 4120000
- flags = 1
- data = length 13, hash B291F16A
- sample 135:
- time = 4140000
- flags = 1
- data = length 13, hash CF820EE0
- sample 136:
- time = 4160000
- flags = 1
- data = length 13, hash 4E24F683
- sample 137:
- time = 4180000
- flags = 1
- data = length 13, hash 52BCD68F
- sample 138:
- time = 4200000
- flags = 1
- data = length 13, hash 42588CB0
- sample 139:
- time = 4220000
- flags = 1
- data = length 13, hash EBBFECA2
- sample 140:
- time = 4240000
- flags = 1
- data = length 13, hash C11050CF
- sample 141:
- time = 4260000
- flags = 1
- data = length 13, hash 6F738603
- sample 142:
- time = 4280000
- flags = 1
- data = length 13, hash DAD06E5
- sample 143:
- time = 4300000
- flags = 1
- data = length 13, hash 5B036C64
- sample 144:
- time = 4320000
- flags = 1
- data = length 13, hash A58DC12E
- sample 145:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.2.dump b/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.2.dump
deleted file mode 100644
index ed109b4..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.2.dump
+++ /dev/null
@@ -1,323 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 4360000
- getPosition(0) = [[timeUs=0, position=6]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 949
- sample count = 73
- sample 0:
- time = 2900000
- flags = 1
- data = length 13, hash FFFBE047
- sample 1:
- time = 2920000
- flags = 1
- data = length 13, hash BEACFCB0
- sample 2:
- time = 2940000
- flags = 1
- data = length 13, hash AEB5096C
- sample 3:
- time = 2960000
- flags = 1
- data = length 13, hash B0D381B
- sample 4:
- time = 2980000
- flags = 1
- data = length 13, hash 3D9D5122
- sample 5:
- time = 3000000
- flags = 1
- data = length 13, hash 6C1DDB95
- sample 6:
- time = 3020000
- flags = 1
- data = length 13, hash ADACADCF
- sample 7:
- time = 3040000
- flags = 1
- data = length 13, hash 159E321E
- sample 8:
- time = 3060000
- flags = 1
- data = length 13, hash B1466264
- sample 9:
- time = 3080000
- flags = 1
- data = length 13, hash 4DDF7223
- sample 10:
- time = 3100000
- flags = 1
- data = length 13, hash C9BDB82A
- sample 11:
- time = 3120000
- flags = 1
- data = length 13, hash A49B2D9D
- sample 12:
- time = 3140000
- flags = 1
- data = length 13, hash D645E7E5
- sample 13:
- time = 3160000
- flags = 1
- data = length 13, hash 1C4232DC
- sample 14:
- time = 3180000
- flags = 1
- data = length 13, hash 83078219
- sample 15:
- time = 3200000
- flags = 1
- data = length 13, hash D6D8B072
- sample 16:
- time = 3220000
- flags = 1
- data = length 13, hash 975DB40
- sample 17:
- time = 3240000
- flags = 1
- data = length 13, hash A15FDD05
- sample 18:
- time = 3260000
- flags = 1
- data = length 13, hash 4B839E41
- sample 19:
- time = 3280000
- flags = 1
- data = length 13, hash 7418F499
- sample 20:
- time = 3300000
- flags = 1
- data = length 13, hash 7A4945E4
- sample 21:
- time = 3320000
- flags = 1
- data = length 13, hash 6249558C
- sample 22:
- time = 3340000
- flags = 1
- data = length 13, hash BD4C5BE3
- sample 23:
- time = 3360000
- flags = 1
- data = length 13, hash BAB30F1D
- sample 24:
- time = 3380000
- flags = 1
- data = length 13, hash 1E1C7012
- sample 25:
- time = 3400000
- flags = 1
- data = length 13, hash 9A3F8A89
- sample 26:
- time = 3420000
- flags = 1
- data = length 13, hash 20BE6D7B
- sample 27:
- time = 3440000
- flags = 1
- data = length 13, hash CAA0591D
- sample 28:
- time = 3460000
- flags = 1
- data = length 13, hash 6D554D17
- sample 29:
- time = 3480000
- flags = 1
- data = length 13, hash D97C3B31
- sample 30:
- time = 3500000
- flags = 1
- data = length 13, hash 75BC5C3
- sample 31:
- time = 3520000
- flags = 1
- data = length 13, hash 7BA1784B
- sample 32:
- time = 3540000
- flags = 1
- data = length 13, hash 1D175D92
- sample 33:
- time = 3560000
- flags = 1
- data = length 13, hash ADCA60FD
- sample 34:
- time = 3580000
- flags = 1
- data = length 13, hash 37018693
- sample 35:
- time = 3600000
- flags = 1
- data = length 13, hash 4553606F
- sample 36:
- time = 3620000
- flags = 1
- data = length 13, hash CF434565
- sample 37:
- time = 3640000
- flags = 1
- data = length 13, hash D264D757
- sample 38:
- time = 3660000
- flags = 1
- data = length 13, hash 4FB493EF
- sample 39:
- time = 3680000
- flags = 1
- data = length 13, hash 919F53A
- sample 40:
- time = 3700000
- flags = 1
- data = length 13, hash C22B009B
- sample 41:
- time = 3720000
- flags = 1
- data = length 13, hash 5981470
- sample 42:
- time = 3740000
- flags = 1
- data = length 13, hash A5D3937C
- sample 43:
- time = 3760000
- flags = 1
- data = length 13, hash A2504429
- sample 44:
- time = 3780000
- flags = 1
- data = length 13, hash AD1B70BE
- sample 45:
- time = 3800000
- flags = 1
- data = length 13, hash 2E39ED5E
- sample 46:
- time = 3820000
- flags = 1
- data = length 13, hash 13A8BE8E
- sample 47:
- time = 3840000
- flags = 1
- data = length 13, hash 1ACD740B
- sample 48:
- time = 3860000
- flags = 1
- data = length 13, hash 80F38B3
- sample 49:
- time = 3880000
- flags = 1
- data = length 13, hash DA9DA79F
- sample 50:
- time = 3900000
- flags = 1
- data = length 13, hash 21B95B7E
- sample 51:
- time = 3920000
- flags = 1
- data = length 13, hash CD22497B
- sample 52:
- time = 3940000
- flags = 1
- data = length 13, hash 718BB35D
- sample 53:
- time = 3960000
- flags = 1
- data = length 13, hash 69ABA6AD
- sample 54:
- time = 3980000
- flags = 1
- data = length 13, hash BAE19549
- sample 55:
- time = 4000000
- flags = 1
- data = length 13, hash 2A792FB3
- sample 56:
- time = 4020000
- flags = 1
- data = length 13, hash 71FCD8
- sample 57:
- time = 4040000
- flags = 1
- data = length 13, hash 44D2B5B3
- sample 58:
- time = 4060000
- flags = 1
- data = length 13, hash 1E87B11B
- sample 59:
- time = 4080000
- flags = 1
- data = length 13, hash 78CD2C11
- sample 60:
- time = 4100000
- flags = 1
- data = length 13, hash 9F198DF0
- sample 61:
- time = 4120000
- flags = 1
- data = length 13, hash B291F16A
- sample 62:
- time = 4140000
- flags = 1
- data = length 13, hash CF820EE0
- sample 63:
- time = 4160000
- flags = 1
- data = length 13, hash 4E24F683
- sample 64:
- time = 4180000
- flags = 1
- data = length 13, hash 52BCD68F
- sample 65:
- time = 4200000
- flags = 1
- data = length 13, hash 42588CB0
- sample 66:
- time = 4220000
- flags = 1
- data = length 13, hash EBBFECA2
- sample 67:
- time = 4240000
- flags = 1
- data = length 13, hash C11050CF
- sample 68:
- time = 4260000
- flags = 1
- data = length 13, hash 6F738603
- sample 69:
- time = 4280000
- flags = 1
- data = length 13, hash DAD06E5
- sample 70:
- time = 4300000
- flags = 1
- data = length 13, hash 5B036C64
- sample 71:
- time = 4320000
- flags = 1
- data = length 13, hash A58DC12E
- sample 72:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.3.dump b/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.3.dump
deleted file mode 100644
index c06de64..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 4360000
- getPosition(0) = [[timeUs=0, position=6]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 13
- sample count = 1
- sample 0:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.unklen.dump b/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.unklen.dump
deleted file mode 100644
index 596f85b..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr.unklen.dump
+++ /dev/null
@@ -1,903 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/3gpp
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 8000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2834
- sample count = 218
- sample 0:
- time = 0
- flags = 1
- data = length 13, hash 371B046C
- sample 1:
- time = 20000
- flags = 1
- data = length 13, hash CE30BF5B
- sample 2:
- time = 40000
- flags = 1
- data = length 13, hash 19A59975
- sample 3:
- time = 60000
- flags = 1
- data = length 13, hash 4879773C
- sample 4:
- time = 80000
- flags = 1
- data = length 13, hash E8F83019
- sample 5:
- time = 100000
- flags = 1
- data = length 13, hash D265CDC9
- sample 6:
- time = 120000
- flags = 1
- data = length 13, hash 91653DAA
- sample 7:
- time = 140000
- flags = 1
- data = length 13, hash C79456F6
- sample 8:
- time = 160000
- flags = 1
- data = length 13, hash CDDC4422
- sample 9:
- time = 180000
- flags = 1
- data = length 13, hash D9ED3AF1
- sample 10:
- time = 200000
- flags = 1
- data = length 13, hash BAB75A33
- sample 11:
- time = 220000
- flags = 1
- data = length 13, hash 2221B4FF
- sample 12:
- time = 240000
- flags = 1
- data = length 13, hash 96400A0B
- sample 13:
- time = 260000
- flags = 1
- data = length 13, hash 582E6FB
- sample 14:
- time = 280000
- flags = 1
- data = length 13, hash C4E878E5
- sample 15:
- time = 300000
- flags = 1
- data = length 13, hash C849A1BD
- sample 16:
- time = 320000
- flags = 1
- data = length 13, hash CFA8A9ED
- sample 17:
- time = 340000
- flags = 1
- data = length 13, hash 70CA4907
- sample 18:
- time = 360000
- flags = 1
- data = length 13, hash B47D4454
- sample 19:
- time = 380000
- flags = 1
- data = length 13, hash 282998C1
- sample 20:
- time = 400000
- flags = 1
- data = length 13, hash 3F3F7A65
- sample 21:
- time = 420000
- flags = 1
- data = length 13, hash CC2EAB58
- sample 22:
- time = 440000
- flags = 1
- data = length 13, hash 279EF712
- sample 23:
- time = 460000
- flags = 1
- data = length 13, hash AA2F4B29
- sample 24:
- time = 480000
- flags = 1
- data = length 13, hash F6F658C4
- sample 25:
- time = 500000
- flags = 1
- data = length 13, hash D7DEBD17
- sample 26:
- time = 520000
- flags = 1
- data = length 13, hash 6DAB9A17
- sample 27:
- time = 540000
- flags = 1
- data = length 13, hash 6ECE1571
- sample 28:
- time = 560000
- flags = 1
- data = length 13, hash B3D0507F
- sample 29:
- time = 580000
- flags = 1
- data = length 13, hash 21E356B9
- sample 30:
- time = 600000
- flags = 1
- data = length 13, hash 410EA12
- sample 31:
- time = 620000
- flags = 1
- data = length 13, hash 533895A8
- sample 32:
- time = 640000
- flags = 1
- data = length 13, hash C61B3E5A
- sample 33:
- time = 660000
- flags = 1
- data = length 13, hash 982170E6
- sample 34:
- time = 680000
- flags = 1
- data = length 13, hash 7A0468C5
- sample 35:
- time = 700000
- flags = 1
- data = length 13, hash 9C85EAA7
- sample 36:
- time = 720000
- flags = 1
- data = length 13, hash B6B341B6
- sample 37:
- time = 740000
- flags = 1
- data = length 13, hash 6937532E
- sample 38:
- time = 760000
- flags = 1
- data = length 13, hash 8CF2A3A0
- sample 39:
- time = 780000
- flags = 1
- data = length 13, hash D2682AC6
- sample 40:
- time = 800000
- flags = 1
- data = length 13, hash BBC5710F
- sample 41:
- time = 820000
- flags = 1
- data = length 13, hash 59080B6C
- sample 42:
- time = 840000
- flags = 1
- data = length 13, hash E4118291
- sample 43:
- time = 860000
- flags = 1
- data = length 13, hash A1E5B296
- sample 44:
- time = 880000
- flags = 1
- data = length 13, hash D7B8F95B
- sample 45:
- time = 900000
- flags = 1
- data = length 13, hash CC839BE1
- sample 46:
- time = 920000
- flags = 1
- data = length 13, hash D459DFCE
- sample 47:
- time = 940000
- flags = 1
- data = length 13, hash D6AD19EC
- sample 48:
- time = 960000
- flags = 1
- data = length 13, hash D05E373D
- sample 49:
- time = 980000
- flags = 1
- data = length 13, hash 6A4460C7
- sample 50:
- time = 1000000
- flags = 1
- data = length 13, hash C9A0D93F
- sample 51:
- time = 1020000
- flags = 1
- data = length 13, hash 3FA819E7
- sample 52:
- time = 1040000
- flags = 1
- data = length 13, hash 1D3CBDFC
- sample 53:
- time = 1060000
- flags = 1
- data = length 13, hash 8BBBB403
- sample 54:
- time = 1080000
- flags = 1
- data = length 13, hash 21B4A0F9
- sample 55:
- time = 1100000
- flags = 1
- data = length 13, hash C0F921D1
- sample 56:
- time = 1120000
- flags = 1
- data = length 13, hash 5D812AAB
- sample 57:
- time = 1140000
- flags = 1
- data = length 13, hash 50C9F3F8
- sample 58:
- time = 1160000
- flags = 1
- data = length 13, hash 5C2BB5D1
- sample 59:
- time = 1180000
- flags = 1
- data = length 13, hash 6BF9BEA5
- sample 60:
- time = 1200000
- flags = 1
- data = length 13, hash 2738C1E6
- sample 61:
- time = 1220000
- flags = 1
- data = length 13, hash 5FC288A6
- sample 62:
- time = 1240000
- flags = 1
- data = length 13, hash 7E8E442A
- sample 63:
- time = 1260000
- flags = 1
- data = length 13, hash AEAA2BBA
- sample 64:
- time = 1280000
- flags = 1
- data = length 13, hash 4E2ACD2F
- sample 65:
- time = 1300000
- flags = 1
- data = length 13, hash D6C90ACF
- sample 66:
- time = 1320000
- flags = 1
- data = length 13, hash 6FD8A944
- sample 67:
- time = 1340000
- flags = 1
- data = length 13, hash A835BBF9
- sample 68:
- time = 1360000
- flags = 1
- data = length 13, hash F7713830
- sample 69:
- time = 1380000
- flags = 1
- data = length 13, hash 3AA966E5
- sample 70:
- time = 1400000
- flags = 1
- data = length 13, hash F939E829
- sample 71:
- time = 1420000
- flags = 1
- data = length 13, hash 7676DE49
- sample 72:
- time = 1440000
- flags = 1
- data = length 13, hash 93BB890A
- sample 73:
- time = 1460000
- flags = 1
- data = length 13, hash B57DBEC8
- sample 74:
- time = 1480000
- flags = 1
- data = length 13, hash 66B0A5B6
- sample 75:
- time = 1500000
- flags = 1
- data = length 13, hash D733E0D
- sample 76:
- time = 1520000
- flags = 1
- data = length 13, hash 80941726
- sample 77:
- time = 1540000
- flags = 1
- data = length 13, hash 556ED633
- sample 78:
- time = 1560000
- flags = 1
- data = length 13, hash C5EDF4E1
- sample 79:
- time = 1580000
- flags = 1
- data = length 13, hash 6B287445
- sample 80:
- time = 1600000
- flags = 1
- data = length 13, hash DC97C4A7
- sample 81:
- time = 1620000
- flags = 1
- data = length 13, hash DA8CBDF4
- sample 82:
- time = 1640000
- flags = 1
- data = length 13, hash 6F60FF77
- sample 83:
- time = 1660000
- flags = 1
- data = length 13, hash 3EB22B96
- sample 84:
- time = 1680000
- flags = 1
- data = length 13, hash B3C31AF5
- sample 85:
- time = 1700000
- flags = 1
- data = length 13, hash 1854AA92
- sample 86:
- time = 1720000
- flags = 1
- data = length 13, hash 6488264B
- sample 87:
- time = 1740000
- flags = 1
- data = length 13, hash 4CC8C5C1
- sample 88:
- time = 1760000
- flags = 1
- data = length 13, hash 19CC7523
- sample 89:
- time = 1780000
- flags = 1
- data = length 13, hash 9BE7B928
- sample 90:
- time = 1800000
- flags = 1
- data = length 13, hash 47EC7CFD
- sample 91:
- time = 1820000
- flags = 1
- data = length 13, hash EC940120
- sample 92:
- time = 1840000
- flags = 1
- data = length 13, hash 73BDA6D0
- sample 93:
- time = 1860000
- flags = 1
- data = length 13, hash FACB3314
- sample 94:
- time = 1880000
- flags = 1
- data = length 13, hash EC61D13B
- sample 95:
- time = 1900000
- flags = 1
- data = length 13, hash B28C7B6C
- sample 96:
- time = 1920000
- flags = 1
- data = length 13, hash B1A4CECD
- sample 97:
- time = 1940000
- flags = 1
- data = length 13, hash 56D41BA6
- sample 98:
- time = 1960000
- flags = 1
- data = length 13, hash 90499F4
- sample 99:
- time = 1980000
- flags = 1
- data = length 13, hash 65D9A9D3
- sample 100:
- time = 2000000
- flags = 1
- data = length 13, hash D9004CC
- sample 101:
- time = 2020000
- flags = 1
- data = length 13, hash 4139C6ED
- sample 102:
- time = 2040000
- flags = 1
- data = length 13, hash C4F8097C
- sample 103:
- time = 2060000
- flags = 1
- data = length 13, hash 94D424FA
- sample 104:
- time = 2080000
- flags = 1
- data = length 13, hash C2C6F5FD
- sample 105:
- time = 2100000
- flags = 1
- data = length 13, hash 15719008
- sample 106:
- time = 2120000
- flags = 1
- data = length 13, hash 4F64F524
- sample 107:
- time = 2140000
- flags = 1
- data = length 13, hash F9E01C1E
- sample 108:
- time = 2160000
- flags = 1
- data = length 13, hash 74C4EE74
- sample 109:
- time = 2180000
- flags = 1
- data = length 13, hash 7EE7553D
- sample 110:
- time = 2200000
- flags = 1
- data = length 13, hash 62DE6539
- sample 111:
- time = 2220000
- flags = 1
- data = length 13, hash 7F5EC222
- sample 112:
- time = 2240000
- flags = 1
- data = length 13, hash 644067F
- sample 113:
- time = 2260000
- flags = 1
- data = length 13, hash CDF6C9DC
- sample 114:
- time = 2280000
- flags = 1
- data = length 13, hash 8B5DBC80
- sample 115:
- time = 2300000
- flags = 1
- data = length 13, hash AD4BBA03
- sample 116:
- time = 2320000
- flags = 1
- data = length 13, hash 7A76340
- sample 117:
- time = 2340000
- flags = 1
- data = length 13, hash 3610F5B0
- sample 118:
- time = 2360000
- flags = 1
- data = length 13, hash 430BC60B
- sample 119:
- time = 2380000
- flags = 1
- data = length 13, hash 99CF1CA6
- sample 120:
- time = 2400000
- flags = 1
- data = length 13, hash 1331C70B
- sample 121:
- time = 2420000
- flags = 1
- data = length 13, hash BD76E69D
- sample 122:
- time = 2440000
- flags = 1
- data = length 13, hash 5DA652AC
- sample 123:
- time = 2460000
- flags = 1
- data = length 13, hash 3B7BF6CE
- sample 124:
- time = 2480000
- flags = 1
- data = length 13, hash ABBFD143
- sample 125:
- time = 2500000
- flags = 1
- data = length 13, hash E9447166
- sample 126:
- time = 2520000
- flags = 1
- data = length 13, hash EC40068C
- sample 127:
- time = 2540000
- flags = 1
- data = length 13, hash A2869400
- sample 128:
- time = 2560000
- flags = 1
- data = length 13, hash C7E0746B
- sample 129:
- time = 2580000
- flags = 1
- data = length 13, hash 60601BB1
- sample 130:
- time = 2600000
- flags = 1
- data = length 13, hash 975AAE9B
- sample 131:
- time = 2620000
- flags = 1
- data = length 13, hash 8BBC0EB2
- sample 132:
- time = 2640000
- flags = 1
- data = length 13, hash 57FB39E5
- sample 133:
- time = 2660000
- flags = 1
- data = length 13, hash 4CDCEEDB
- sample 134:
- time = 2680000
- flags = 1
- data = length 13, hash EA16E256
- sample 135:
- time = 2700000
- flags = 1
- data = length 13, hash 287E7D9E
- sample 136:
- time = 2720000
- flags = 1
- data = length 13, hash 55AB8FB9
- sample 137:
- time = 2740000
- flags = 1
- data = length 13, hash 129890EF
- sample 138:
- time = 2760000
- flags = 1
- data = length 13, hash 90834F57
- sample 139:
- time = 2780000
- flags = 1
- data = length 13, hash 5B3228E0
- sample 140:
- time = 2800000
- flags = 1
- data = length 13, hash DD19E175
- sample 141:
- time = 2820000
- flags = 1
- data = length 13, hash EE7EA342
- sample 142:
- time = 2840000
- flags = 1
- data = length 13, hash DB3AF473
- sample 143:
- time = 2860000
- flags = 1
- data = length 13, hash 25AEC43F
- sample 144:
- time = 2880000
- flags = 1
- data = length 13, hash EE9BF97F
- sample 145:
- time = 2900000
- flags = 1
- data = length 13, hash FFFBE047
- sample 146:
- time = 2920000
- flags = 1
- data = length 13, hash BEACFCB0
- sample 147:
- time = 2940000
- flags = 1
- data = length 13, hash AEB5096C
- sample 148:
- time = 2960000
- flags = 1
- data = length 13, hash B0D381B
- sample 149:
- time = 2980000
- flags = 1
- data = length 13, hash 3D9D5122
- sample 150:
- time = 3000000
- flags = 1
- data = length 13, hash 6C1DDB95
- sample 151:
- time = 3020000
- flags = 1
- data = length 13, hash ADACADCF
- sample 152:
- time = 3040000
- flags = 1
- data = length 13, hash 159E321E
- sample 153:
- time = 3060000
- flags = 1
- data = length 13, hash B1466264
- sample 154:
- time = 3080000
- flags = 1
- data = length 13, hash 4DDF7223
- sample 155:
- time = 3100000
- flags = 1
- data = length 13, hash C9BDB82A
- sample 156:
- time = 3120000
- flags = 1
- data = length 13, hash A49B2D9D
- sample 157:
- time = 3140000
- flags = 1
- data = length 13, hash D645E7E5
- sample 158:
- time = 3160000
- flags = 1
- data = length 13, hash 1C4232DC
- sample 159:
- time = 3180000
- flags = 1
- data = length 13, hash 83078219
- sample 160:
- time = 3200000
- flags = 1
- data = length 13, hash D6D8B072
- sample 161:
- time = 3220000
- flags = 1
- data = length 13, hash 975DB40
- sample 162:
- time = 3240000
- flags = 1
- data = length 13, hash A15FDD05
- sample 163:
- time = 3260000
- flags = 1
- data = length 13, hash 4B839E41
- sample 164:
- time = 3280000
- flags = 1
- data = length 13, hash 7418F499
- sample 165:
- time = 3300000
- flags = 1
- data = length 13, hash 7A4945E4
- sample 166:
- time = 3320000
- flags = 1
- data = length 13, hash 6249558C
- sample 167:
- time = 3340000
- flags = 1
- data = length 13, hash BD4C5BE3
- sample 168:
- time = 3360000
- flags = 1
- data = length 13, hash BAB30F1D
- sample 169:
- time = 3380000
- flags = 1
- data = length 13, hash 1E1C7012
- sample 170:
- time = 3400000
- flags = 1
- data = length 13, hash 9A3F8A89
- sample 171:
- time = 3420000
- flags = 1
- data = length 13, hash 20BE6D7B
- sample 172:
- time = 3440000
- flags = 1
- data = length 13, hash CAA0591D
- sample 173:
- time = 3460000
- flags = 1
- data = length 13, hash 6D554D17
- sample 174:
- time = 3480000
- flags = 1
- data = length 13, hash D97C3B31
- sample 175:
- time = 3500000
- flags = 1
- data = length 13, hash 75BC5C3
- sample 176:
- time = 3520000
- flags = 1
- data = length 13, hash 7BA1784B
- sample 177:
- time = 3540000
- flags = 1
- data = length 13, hash 1D175D92
- sample 178:
- time = 3560000
- flags = 1
- data = length 13, hash ADCA60FD
- sample 179:
- time = 3580000
- flags = 1
- data = length 13, hash 37018693
- sample 180:
- time = 3600000
- flags = 1
- data = length 13, hash 4553606F
- sample 181:
- time = 3620000
- flags = 1
- data = length 13, hash CF434565
- sample 182:
- time = 3640000
- flags = 1
- data = length 13, hash D264D757
- sample 183:
- time = 3660000
- flags = 1
- data = length 13, hash 4FB493EF
- sample 184:
- time = 3680000
- flags = 1
- data = length 13, hash 919F53A
- sample 185:
- time = 3700000
- flags = 1
- data = length 13, hash C22B009B
- sample 186:
- time = 3720000
- flags = 1
- data = length 13, hash 5981470
- sample 187:
- time = 3740000
- flags = 1
- data = length 13, hash A5D3937C
- sample 188:
- time = 3760000
- flags = 1
- data = length 13, hash A2504429
- sample 189:
- time = 3780000
- flags = 1
- data = length 13, hash AD1B70BE
- sample 190:
- time = 3800000
- flags = 1
- data = length 13, hash 2E39ED5E
- sample 191:
- time = 3820000
- flags = 1
- data = length 13, hash 13A8BE8E
- sample 192:
- time = 3840000
- flags = 1
- data = length 13, hash 1ACD740B
- sample 193:
- time = 3860000
- flags = 1
- data = length 13, hash 80F38B3
- sample 194:
- time = 3880000
- flags = 1
- data = length 13, hash DA9DA79F
- sample 195:
- time = 3900000
- flags = 1
- data = length 13, hash 21B95B7E
- sample 196:
- time = 3920000
- flags = 1
- data = length 13, hash CD22497B
- sample 197:
- time = 3940000
- flags = 1
- data = length 13, hash 718BB35D
- sample 198:
- time = 3960000
- flags = 1
- data = length 13, hash 69ABA6AD
- sample 199:
- time = 3980000
- flags = 1
- data = length 13, hash BAE19549
- sample 200:
- time = 4000000
- flags = 1
- data = length 13, hash 2A792FB3
- sample 201:
- time = 4020000
- flags = 1
- data = length 13, hash 71FCD8
- sample 202:
- time = 4040000
- flags = 1
- data = length 13, hash 44D2B5B3
- sample 203:
- time = 4060000
- flags = 1
- data = length 13, hash 1E87B11B
- sample 204:
- time = 4080000
- flags = 1
- data = length 13, hash 78CD2C11
- sample 205:
- time = 4100000
- flags = 1
- data = length 13, hash 9F198DF0
- sample 206:
- time = 4120000
- flags = 1
- data = length 13, hash B291F16A
- sample 207:
- time = 4140000
- flags = 1
- data = length 13, hash CF820EE0
- sample 208:
- time = 4160000
- flags = 1
- data = length 13, hash 4E24F683
- sample 209:
- time = 4180000
- flags = 1
- data = length 13, hash 52BCD68F
- sample 210:
- time = 4200000
- flags = 1
- data = length 13, hash 42588CB0
- sample 211:
- time = 4220000
- flags = 1
- data = length 13, hash EBBFECA2
- sample 212:
- time = 4240000
- flags = 1
- data = length 13, hash C11050CF
- sample 213:
- time = 4260000
- flags = 1
- data = length 13, hash 6F738603
- sample 214:
- time = 4280000
- flags = 1
- data = length 13, hash DAD06E5
- sample 215:
- time = 4300000
- flags = 1
- data = length 13, hash 5B036C64
- sample 216:
- time = 4320000
- flags = 1
- data = length 13, hash A58DC12E
- sample 217:
- time = 4340000
- flags = 1
- data = length 13, hash AC59BA7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb.amr.0.dump b/tree/library/extractor/src/test/assets/amr/sample_wb.amr.0.dump
deleted file mode 100644
index c744d9b..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb.amr.0.dump
+++ /dev/null
@@ -1,707 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 4056
- sample count = 169
- sample 0:
- time = 0
- flags = 1
- data = length 24, hash C3025798
- sample 1:
- time = 20000
- flags = 1
- data = length 24, hash 39CABAE9
- sample 2:
- time = 40000
- flags = 1
- data = length 24, hash 2752F470
- sample 3:
- time = 60000
- flags = 1
- data = length 24, hash 394F76F6
- sample 4:
- time = 80000
- flags = 1
- data = length 24, hash FF9EEF
- sample 5:
- time = 100000
- flags = 1
- data = length 24, hash 54ECB1B4
- sample 6:
- time = 120000
- flags = 1
- data = length 24, hash 6D7A3A5F
- sample 7:
- time = 140000
- flags = 1
- data = length 24, hash 684CD144
- sample 8:
- time = 160000
- flags = 1
- data = length 24, hash 87B7D176
- sample 9:
- time = 180000
- flags = 1
- data = length 24, hash 4C02F9A5
- sample 10:
- time = 200000
- flags = 1
- data = length 24, hash B4154108
- sample 11:
- time = 220000
- flags = 1
- data = length 24, hash 4448F477
- sample 12:
- time = 240000
- flags = 1
- data = length 24, hash 755A4939
- sample 13:
- time = 260000
- flags = 1
- data = length 24, hash 8C8BC6C3
- sample 14:
- time = 280000
- flags = 1
- data = length 24, hash BC37F63F
- sample 15:
- time = 300000
- flags = 1
- data = length 24, hash 3352C43C
- sample 16:
- time = 320000
- flags = 1
- data = length 24, hash 7998E1F2
- sample 17:
- time = 340000
- flags = 1
- data = length 24, hash A8ECBEFC
- sample 18:
- time = 360000
- flags = 1
- data = length 24, hash 944AC118
- sample 19:
- time = 380000
- flags = 1
- data = length 24, hash FD2C8E1F
- sample 20:
- time = 400000
- flags = 1
- data = length 24, hash B3D867AF
- sample 21:
- time = 420000
- flags = 1
- data = length 24, hash 3DC6E592
- sample 22:
- time = 440000
- flags = 1
- data = length 24, hash 32B276CD
- sample 23:
- time = 460000
- flags = 1
- data = length 24, hash 5488AEF3
- sample 24:
- time = 480000
- flags = 1
- data = length 24, hash 7A4D516
- sample 25:
- time = 500000
- flags = 1
- data = length 24, hash 570AE83F
- sample 26:
- time = 520000
- flags = 1
- data = length 24, hash E5CB3477
- sample 27:
- time = 540000
- flags = 1
- data = length 24, hash E04C00E4
- sample 28:
- time = 560000
- flags = 1
- data = length 24, hash 21B7C97
- sample 29:
- time = 580000
- flags = 1
- data = length 24, hash 1633F470
- sample 30:
- time = 600000
- flags = 1
- data = length 24, hash 28D65CA6
- sample 31:
- time = 620000
- flags = 1
- data = length 24, hash CC6A675C
- sample 32:
- time = 640000
- flags = 1
- data = length 24, hash 4C91080A
- sample 33:
- time = 660000
- flags = 1
- data = length 24, hash F6482FB5
- sample 34:
- time = 680000
- flags = 1
- data = length 24, hash 2C76F48C
- sample 35:
- time = 700000
- flags = 1
- data = length 24, hash 6E3B0D72
- sample 36:
- time = 720000
- flags = 1
- data = length 24, hash 799AA003
- sample 37:
- time = 740000
- flags = 1
- data = length 24, hash DFC0BA81
- sample 38:
- time = 760000
- flags = 1
- data = length 24, hash CBDF3826
- sample 39:
- time = 780000
- flags = 1
- data = length 24, hash 16862B75
- sample 40:
- time = 800000
- flags = 1
- data = length 24, hash 865A828E
- sample 41:
- time = 820000
- flags = 1
- data = length 24, hash 336BBDC9
- sample 42:
- time = 840000
- flags = 1
- data = length 24, hash 6CFC6C34
- sample 43:
- time = 860000
- flags = 1
- data = length 24, hash 32C8CD46
- sample 44:
- time = 880000
- flags = 1
- data = length 24, hash 9FE11C4C
- sample 45:
- time = 900000
- flags = 1
- data = length 24, hash AA5A12B7
- sample 46:
- time = 920000
- flags = 1
- data = length 24, hash AA0F4A4D
- sample 47:
- time = 940000
- flags = 1
- data = length 24, hash 34415484
- sample 48:
- time = 960000
- flags = 1
- data = length 24, hash 5018928E
- sample 49:
- time = 980000
- flags = 1
- data = length 24, hash 4A04D162
- sample 50:
- time = 1000000
- flags = 1
- data = length 24, hash 4C70F9F0
- sample 51:
- time = 1020000
- flags = 1
- data = length 24, hash 99EF3168
- sample 52:
- time = 1040000
- flags = 1
- data = length 24, hash C600DAF
- sample 53:
- time = 1060000
- flags = 1
- data = length 24, hash FDBB192E
- sample 54:
- time = 1080000
- flags = 1
- data = length 24, hash 99096A48
- sample 55:
- time = 1100000
- flags = 1
- data = length 24, hash D793F88B
- sample 56:
- time = 1120000
- flags = 1
- data = length 24, hash EEB921BD
- sample 57:
- time = 1140000
- flags = 1
- data = length 24, hash 8B941A4C
- sample 58:
- time = 1160000
- flags = 1
- data = length 24, hash ED5F5FEE
- sample 59:
- time = 1180000
- flags = 1
- data = length 24, hash A588E0BB
- sample 60:
- time = 1200000
- flags = 1
- data = length 24, hash 588CBC01
- sample 61:
- time = 1220000
- flags = 1
- data = length 24, hash DE22266C
- sample 62:
- time = 1240000
- flags = 1
- data = length 24, hash 921B6E5C
- sample 63:
- time = 1260000
- flags = 1
- data = length 24, hash EC11F041
- sample 64:
- time = 1280000
- flags = 1
- data = length 24, hash 5BA9E0A3
- sample 65:
- time = 1300000
- flags = 1
- data = length 24, hash DB6D52F3
- sample 66:
- time = 1320000
- flags = 1
- data = length 24, hash 8EEBE525
- sample 67:
- time = 1340000
- flags = 1
- data = length 24, hash 47A742AE
- sample 68:
- time = 1360000
- flags = 1
- data = length 24, hash E93F1E03
- sample 69:
- time = 1380000
- flags = 1
- data = length 24, hash 3251F57C
- sample 70:
- time = 1400000
- flags = 1
- data = length 24, hash 3EDBBBDD
- sample 71:
- time = 1420000
- flags = 1
- data = length 24, hash 2E98465A
- sample 72:
- time = 1440000
- flags = 1
- data = length 24, hash A09EA52E
- sample 73:
- time = 1460000
- flags = 1
- data = length 24, hash A2A86FA6
- sample 74:
- time = 1480000
- flags = 1
- data = length 24, hash 71DCD51C
- sample 75:
- time = 1500000
- flags = 1
- data = length 24, hash 2B02DEE1
- sample 76:
- time = 1520000
- flags = 1
- data = length 24, hash 7A725192
- sample 77:
- time = 1540000
- flags = 1
- data = length 24, hash 929AD483
- sample 78:
- time = 1560000
- flags = 1
- data = length 24, hash 68440BF5
- sample 79:
- time = 1580000
- flags = 1
- data = length 24, hash 5BD41AD6
- sample 80:
- time = 1600000
- flags = 1
- data = length 24, hash 91A381
- sample 81:
- time = 1620000
- flags = 1
- data = length 24, hash 8010C408
- sample 82:
- time = 1640000
- flags = 1
- data = length 24, hash 482274BE
- sample 83:
- time = 1660000
- flags = 1
- data = length 24, hash D7DB8BCC
- sample 84:
- time = 1680000
- flags = 1
- data = length 24, hash 680BD9DD
- sample 85:
- time = 1700000
- flags = 1
- data = length 24, hash E313577C
- sample 86:
- time = 1720000
- flags = 1
- data = length 24, hash 9C10B0CD
- sample 87:
- time = 1740000
- flags = 1
- data = length 24, hash 2D90AC02
- sample 88:
- time = 1760000
- flags = 1
- data = length 24, hash 64E8C245
- sample 89:
- time = 1780000
- flags = 1
- data = length 24, hash 3954AC1B
- sample 90:
- time = 1800000
- flags = 1
- data = length 24, hash ACB8999F
- sample 91:
- time = 1820000
- flags = 1
- data = length 24, hash 43AE3957
- sample 92:
- time = 1840000
- flags = 1
- data = length 24, hash 3C664DB7
- sample 93:
- time = 1860000
- flags = 1
- data = length 24, hash 9354B576
- sample 94:
- time = 1880000
- flags = 1
- data = length 24, hash B5B9C14E
- sample 95:
- time = 1900000
- flags = 1
- data = length 24, hash 7DA9C98F
- sample 96:
- time = 1920000
- flags = 1
- data = length 24, hash EFEE54C6
- sample 97:
- time = 1940000
- flags = 1
- data = length 24, hash 79DC8CBD
- sample 98:
- time = 1960000
- flags = 1
- data = length 24, hash A71A475C
- sample 99:
- time = 1980000
- flags = 1
- data = length 24, hash CA1CBB94
- sample 100:
- time = 2000000
- flags = 1
- data = length 24, hash 91922226
- sample 101:
- time = 2020000
- flags = 1
- data = length 24, hash C90278BC
- sample 102:
- time = 2040000
- flags = 1
- data = length 24, hash BD51986F
- sample 103:
- time = 2060000
- flags = 1
- data = length 24, hash 90AEF368
- sample 104:
- time = 2080000
- flags = 1
- data = length 24, hash 1D83C955
- sample 105:
- time = 2100000
- flags = 1
- data = length 24, hash 8FA9A915
- sample 106:
- time = 2120000
- flags = 1
- data = length 24, hash C6C753E0
- sample 107:
- time = 2140000
- flags = 1
- data = length 24, hash 85FA27A7
- sample 108:
- time = 2160000
- flags = 1
- data = length 24, hash A0277324
- sample 109:
- time = 2180000
- flags = 1
- data = length 24, hash B7696535
- sample 110:
- time = 2200000
- flags = 1
- data = length 24, hash D69D668C
- sample 111:
- time = 2220000
- flags = 1
- data = length 24, hash 34C057CD
- sample 112:
- time = 2240000
- flags = 1
- data = length 24, hash 4EC5E974
- sample 113:
- time = 2260000
- flags = 1
- data = length 24, hash 1C1CD40D
- sample 114:
- time = 2280000
- flags = 1
- data = length 24, hash 76CC54BC
- sample 115:
- time = 2300000
- flags = 1
- data = length 24, hash D497ACF5
- sample 116:
- time = 2320000
- flags = 1
- data = length 24, hash A1386080
- sample 117:
- time = 2340000
- flags = 1
- data = length 24, hash 7ED36954
- sample 118:
- time = 2360000
- flags = 1
- data = length 24, hash C11A3BF9
- sample 119:
- time = 2380000
- flags = 1
- data = length 24, hash 8FB69488
- sample 120:
- time = 2400000
- flags = 1
- data = length 24, hash C6225F59
- sample 121:
- time = 2420000
- flags = 1
- data = length 24, hash 122AB6D2
- sample 122:
- time = 2440000
- flags = 1
- data = length 24, hash 1E195E7D
- sample 123:
- time = 2460000
- flags = 1
- data = length 24, hash BD3DF418
- sample 124:
- time = 2480000
- flags = 1
- data = length 24, hash D8AE4A5
- sample 125:
- time = 2500000
- flags = 1
- data = length 24, hash 977BD182
- sample 126:
- time = 2520000
- flags = 1
- data = length 24, hash F361F060
- sample 127:
- time = 2540000
- flags = 1
- data = length 24, hash 11EC8CD0
- sample 128:
- time = 2560000
- flags = 1
- data = length 24, hash 3798F3D2
- sample 129:
- time = 2580000
- flags = 1
- data = length 24, hash B2C2517C
- sample 130:
- time = 2600000
- flags = 1
- data = length 24, hash FBE0D0D8
- sample 131:
- time = 2620000
- flags = 1
- data = length 24, hash 7033172F
- sample 132:
- time = 2640000
- flags = 1
- data = length 24, hash BE760029
- sample 133:
- time = 2660000
- flags = 1
- data = length 24, hash 590AF28C
- sample 134:
- time = 2680000
- flags = 1
- data = length 24, hash AD28C48F
- sample 135:
- time = 2700000
- flags = 1
- data = length 24, hash 640AA61B
- sample 136:
- time = 2720000
- flags = 1
- data = length 24, hash ABE659B
- sample 137:
- time = 2740000
- flags = 1
- data = length 24, hash ED2691D2
- sample 138:
- time = 2760000
- flags = 1
- data = length 24, hash D998C80E
- sample 139:
- time = 2780000
- flags = 1
- data = length 24, hash 8DC0DF5C
- sample 140:
- time = 2800000
- flags = 1
- data = length 24, hash 7692247B
- sample 141:
- time = 2820000
- flags = 1
- data = length 24, hash C1D1CCB9
- sample 142:
- time = 2840000
- flags = 1
- data = length 24, hash 362CE78E
- sample 143:
- time = 2860000
- flags = 1
- data = length 24, hash 54FA84A
- sample 144:
- time = 2880000
- flags = 1
- data = length 24, hash 29E88C84
- sample 145:
- time = 2900000
- flags = 1
- data = length 24, hash 1CD848AC
- sample 146:
- time = 2920000
- flags = 1
- data = length 24, hash 5C3D4A79
- sample 147:
- time = 2940000
- flags = 1
- data = length 24, hash 1AA8E604
- sample 148:
- time = 2960000
- flags = 1
- data = length 24, hash 186A4316
- sample 149:
- time = 2980000
- flags = 1
- data = length 24, hash 61ACE481
- sample 150:
- time = 3000000
- flags = 1
- data = length 24, hash D0C42780
- sample 151:
- time = 3020000
- flags = 1
- data = length 24, hash FAD51BA1
- sample 152:
- time = 3040000
- flags = 1
- data = length 24, hash F1A9AC71
- sample 153:
- time = 3060000
- flags = 1
- data = length 24, hash 24425449
- sample 154:
- time = 3080000
- flags = 1
- data = length 24, hash 37AAC3E6
- sample 155:
- time = 3100000
- flags = 1
- data = length 24, hash 91F68CB4
- sample 156:
- time = 3120000
- flags = 1
- data = length 24, hash F8C92820
- sample 157:
- time = 3140000
- flags = 1
- data = length 24, hash ECD39C3E
- sample 158:
- time = 3160000
- flags = 1
- data = length 24, hash B27D8F78
- sample 159:
- time = 3180000
- flags = 1
- data = length 24, hash C9EB3DFB
- sample 160:
- time = 3200000
- flags = 1
- data = length 24, hash 88DC54A2
- sample 161:
- time = 3220000
- flags = 1
- data = length 24, hash 7FC4C5BE
- sample 162:
- time = 3240000
- flags = 1
- data = length 24, hash E4F684EF
- sample 163:
- time = 3260000
- flags = 1
- data = length 24, hash 55C08B56
- sample 164:
- time = 3280000
- flags = 1
- data = length 24, hash E5A0F006
- sample 165:
- time = 3300000
- flags = 1
- data = length 24, hash DE3F3AA7
- sample 166:
- time = 3320000
- flags = 1
- data = length 24, hash 3F28AE7F
- sample 167:
- time = 3340000
- flags = 1
- data = length 24, hash 3949CAFF
- sample 168:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.0.dump b/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.0.dump
deleted file mode 100644
index 71c6868..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.0.dump
+++ /dev/null
@@ -1,707 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3380000
- getPosition(0) = [[timeUs=0, position=9]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 4056
- sample count = 169
- sample 0:
- time = 0
- flags = 1
- data = length 24, hash C3025798
- sample 1:
- time = 20000
- flags = 1
- data = length 24, hash 39CABAE9
- sample 2:
- time = 40000
- flags = 1
- data = length 24, hash 2752F470
- sample 3:
- time = 60000
- flags = 1
- data = length 24, hash 394F76F6
- sample 4:
- time = 80000
- flags = 1
- data = length 24, hash FF9EEF
- sample 5:
- time = 100000
- flags = 1
- data = length 24, hash 54ECB1B4
- sample 6:
- time = 120000
- flags = 1
- data = length 24, hash 6D7A3A5F
- sample 7:
- time = 140000
- flags = 1
- data = length 24, hash 684CD144
- sample 8:
- time = 160000
- flags = 1
- data = length 24, hash 87B7D176
- sample 9:
- time = 180000
- flags = 1
- data = length 24, hash 4C02F9A5
- sample 10:
- time = 200000
- flags = 1
- data = length 24, hash B4154108
- sample 11:
- time = 220000
- flags = 1
- data = length 24, hash 4448F477
- sample 12:
- time = 240000
- flags = 1
- data = length 24, hash 755A4939
- sample 13:
- time = 260000
- flags = 1
- data = length 24, hash 8C8BC6C3
- sample 14:
- time = 280000
- flags = 1
- data = length 24, hash BC37F63F
- sample 15:
- time = 300000
- flags = 1
- data = length 24, hash 3352C43C
- sample 16:
- time = 320000
- flags = 1
- data = length 24, hash 7998E1F2
- sample 17:
- time = 340000
- flags = 1
- data = length 24, hash A8ECBEFC
- sample 18:
- time = 360000
- flags = 1
- data = length 24, hash 944AC118
- sample 19:
- time = 380000
- flags = 1
- data = length 24, hash FD2C8E1F
- sample 20:
- time = 400000
- flags = 1
- data = length 24, hash B3D867AF
- sample 21:
- time = 420000
- flags = 1
- data = length 24, hash 3DC6E592
- sample 22:
- time = 440000
- flags = 1
- data = length 24, hash 32B276CD
- sample 23:
- time = 460000
- flags = 1
- data = length 24, hash 5488AEF3
- sample 24:
- time = 480000
- flags = 1
- data = length 24, hash 7A4D516
- sample 25:
- time = 500000
- flags = 1
- data = length 24, hash 570AE83F
- sample 26:
- time = 520000
- flags = 1
- data = length 24, hash E5CB3477
- sample 27:
- time = 540000
- flags = 1
- data = length 24, hash E04C00E4
- sample 28:
- time = 560000
- flags = 1
- data = length 24, hash 21B7C97
- sample 29:
- time = 580000
- flags = 1
- data = length 24, hash 1633F470
- sample 30:
- time = 600000
- flags = 1
- data = length 24, hash 28D65CA6
- sample 31:
- time = 620000
- flags = 1
- data = length 24, hash CC6A675C
- sample 32:
- time = 640000
- flags = 1
- data = length 24, hash 4C91080A
- sample 33:
- time = 660000
- flags = 1
- data = length 24, hash F6482FB5
- sample 34:
- time = 680000
- flags = 1
- data = length 24, hash 2C76F48C
- sample 35:
- time = 700000
- flags = 1
- data = length 24, hash 6E3B0D72
- sample 36:
- time = 720000
- flags = 1
- data = length 24, hash 799AA003
- sample 37:
- time = 740000
- flags = 1
- data = length 24, hash DFC0BA81
- sample 38:
- time = 760000
- flags = 1
- data = length 24, hash CBDF3826
- sample 39:
- time = 780000
- flags = 1
- data = length 24, hash 16862B75
- sample 40:
- time = 800000
- flags = 1
- data = length 24, hash 865A828E
- sample 41:
- time = 820000
- flags = 1
- data = length 24, hash 336BBDC9
- sample 42:
- time = 840000
- flags = 1
- data = length 24, hash 6CFC6C34
- sample 43:
- time = 860000
- flags = 1
- data = length 24, hash 32C8CD46
- sample 44:
- time = 880000
- flags = 1
- data = length 24, hash 9FE11C4C
- sample 45:
- time = 900000
- flags = 1
- data = length 24, hash AA5A12B7
- sample 46:
- time = 920000
- flags = 1
- data = length 24, hash AA0F4A4D
- sample 47:
- time = 940000
- flags = 1
- data = length 24, hash 34415484
- sample 48:
- time = 960000
- flags = 1
- data = length 24, hash 5018928E
- sample 49:
- time = 980000
- flags = 1
- data = length 24, hash 4A04D162
- sample 50:
- time = 1000000
- flags = 1
- data = length 24, hash 4C70F9F0
- sample 51:
- time = 1020000
- flags = 1
- data = length 24, hash 99EF3168
- sample 52:
- time = 1040000
- flags = 1
- data = length 24, hash C600DAF
- sample 53:
- time = 1060000
- flags = 1
- data = length 24, hash FDBB192E
- sample 54:
- time = 1080000
- flags = 1
- data = length 24, hash 99096A48
- sample 55:
- time = 1100000
- flags = 1
- data = length 24, hash D793F88B
- sample 56:
- time = 1120000
- flags = 1
- data = length 24, hash EEB921BD
- sample 57:
- time = 1140000
- flags = 1
- data = length 24, hash 8B941A4C
- sample 58:
- time = 1160000
- flags = 1
- data = length 24, hash ED5F5FEE
- sample 59:
- time = 1180000
- flags = 1
- data = length 24, hash A588E0BB
- sample 60:
- time = 1200000
- flags = 1
- data = length 24, hash 588CBC01
- sample 61:
- time = 1220000
- flags = 1
- data = length 24, hash DE22266C
- sample 62:
- time = 1240000
- flags = 1
- data = length 24, hash 921B6E5C
- sample 63:
- time = 1260000
- flags = 1
- data = length 24, hash EC11F041
- sample 64:
- time = 1280000
- flags = 1
- data = length 24, hash 5BA9E0A3
- sample 65:
- time = 1300000
- flags = 1
- data = length 24, hash DB6D52F3
- sample 66:
- time = 1320000
- flags = 1
- data = length 24, hash 8EEBE525
- sample 67:
- time = 1340000
- flags = 1
- data = length 24, hash 47A742AE
- sample 68:
- time = 1360000
- flags = 1
- data = length 24, hash E93F1E03
- sample 69:
- time = 1380000
- flags = 1
- data = length 24, hash 3251F57C
- sample 70:
- time = 1400000
- flags = 1
- data = length 24, hash 3EDBBBDD
- sample 71:
- time = 1420000
- flags = 1
- data = length 24, hash 2E98465A
- sample 72:
- time = 1440000
- flags = 1
- data = length 24, hash A09EA52E
- sample 73:
- time = 1460000
- flags = 1
- data = length 24, hash A2A86FA6
- sample 74:
- time = 1480000
- flags = 1
- data = length 24, hash 71DCD51C
- sample 75:
- time = 1500000
- flags = 1
- data = length 24, hash 2B02DEE1
- sample 76:
- time = 1520000
- flags = 1
- data = length 24, hash 7A725192
- sample 77:
- time = 1540000
- flags = 1
- data = length 24, hash 929AD483
- sample 78:
- time = 1560000
- flags = 1
- data = length 24, hash 68440BF5
- sample 79:
- time = 1580000
- flags = 1
- data = length 24, hash 5BD41AD6
- sample 80:
- time = 1600000
- flags = 1
- data = length 24, hash 91A381
- sample 81:
- time = 1620000
- flags = 1
- data = length 24, hash 8010C408
- sample 82:
- time = 1640000
- flags = 1
- data = length 24, hash 482274BE
- sample 83:
- time = 1660000
- flags = 1
- data = length 24, hash D7DB8BCC
- sample 84:
- time = 1680000
- flags = 1
- data = length 24, hash 680BD9DD
- sample 85:
- time = 1700000
- flags = 1
- data = length 24, hash E313577C
- sample 86:
- time = 1720000
- flags = 1
- data = length 24, hash 9C10B0CD
- sample 87:
- time = 1740000
- flags = 1
- data = length 24, hash 2D90AC02
- sample 88:
- time = 1760000
- flags = 1
- data = length 24, hash 64E8C245
- sample 89:
- time = 1780000
- flags = 1
- data = length 24, hash 3954AC1B
- sample 90:
- time = 1800000
- flags = 1
- data = length 24, hash ACB8999F
- sample 91:
- time = 1820000
- flags = 1
- data = length 24, hash 43AE3957
- sample 92:
- time = 1840000
- flags = 1
- data = length 24, hash 3C664DB7
- sample 93:
- time = 1860000
- flags = 1
- data = length 24, hash 9354B576
- sample 94:
- time = 1880000
- flags = 1
- data = length 24, hash B5B9C14E
- sample 95:
- time = 1900000
- flags = 1
- data = length 24, hash 7DA9C98F
- sample 96:
- time = 1920000
- flags = 1
- data = length 24, hash EFEE54C6
- sample 97:
- time = 1940000
- flags = 1
- data = length 24, hash 79DC8CBD
- sample 98:
- time = 1960000
- flags = 1
- data = length 24, hash A71A475C
- sample 99:
- time = 1980000
- flags = 1
- data = length 24, hash CA1CBB94
- sample 100:
- time = 2000000
- flags = 1
- data = length 24, hash 91922226
- sample 101:
- time = 2020000
- flags = 1
- data = length 24, hash C90278BC
- sample 102:
- time = 2040000
- flags = 1
- data = length 24, hash BD51986F
- sample 103:
- time = 2060000
- flags = 1
- data = length 24, hash 90AEF368
- sample 104:
- time = 2080000
- flags = 1
- data = length 24, hash 1D83C955
- sample 105:
- time = 2100000
- flags = 1
- data = length 24, hash 8FA9A915
- sample 106:
- time = 2120000
- flags = 1
- data = length 24, hash C6C753E0
- sample 107:
- time = 2140000
- flags = 1
- data = length 24, hash 85FA27A7
- sample 108:
- time = 2160000
- flags = 1
- data = length 24, hash A0277324
- sample 109:
- time = 2180000
- flags = 1
- data = length 24, hash B7696535
- sample 110:
- time = 2200000
- flags = 1
- data = length 24, hash D69D668C
- sample 111:
- time = 2220000
- flags = 1
- data = length 24, hash 34C057CD
- sample 112:
- time = 2240000
- flags = 1
- data = length 24, hash 4EC5E974
- sample 113:
- time = 2260000
- flags = 1
- data = length 24, hash 1C1CD40D
- sample 114:
- time = 2280000
- flags = 1
- data = length 24, hash 76CC54BC
- sample 115:
- time = 2300000
- flags = 1
- data = length 24, hash D497ACF5
- sample 116:
- time = 2320000
- flags = 1
- data = length 24, hash A1386080
- sample 117:
- time = 2340000
- flags = 1
- data = length 24, hash 7ED36954
- sample 118:
- time = 2360000
- flags = 1
- data = length 24, hash C11A3BF9
- sample 119:
- time = 2380000
- flags = 1
- data = length 24, hash 8FB69488
- sample 120:
- time = 2400000
- flags = 1
- data = length 24, hash C6225F59
- sample 121:
- time = 2420000
- flags = 1
- data = length 24, hash 122AB6D2
- sample 122:
- time = 2440000
- flags = 1
- data = length 24, hash 1E195E7D
- sample 123:
- time = 2460000
- flags = 1
- data = length 24, hash BD3DF418
- sample 124:
- time = 2480000
- flags = 1
- data = length 24, hash D8AE4A5
- sample 125:
- time = 2500000
- flags = 1
- data = length 24, hash 977BD182
- sample 126:
- time = 2520000
- flags = 1
- data = length 24, hash F361F060
- sample 127:
- time = 2540000
- flags = 1
- data = length 24, hash 11EC8CD0
- sample 128:
- time = 2560000
- flags = 1
- data = length 24, hash 3798F3D2
- sample 129:
- time = 2580000
- flags = 1
- data = length 24, hash B2C2517C
- sample 130:
- time = 2600000
- flags = 1
- data = length 24, hash FBE0D0D8
- sample 131:
- time = 2620000
- flags = 1
- data = length 24, hash 7033172F
- sample 132:
- time = 2640000
- flags = 1
- data = length 24, hash BE760029
- sample 133:
- time = 2660000
- flags = 1
- data = length 24, hash 590AF28C
- sample 134:
- time = 2680000
- flags = 1
- data = length 24, hash AD28C48F
- sample 135:
- time = 2700000
- flags = 1
- data = length 24, hash 640AA61B
- sample 136:
- time = 2720000
- flags = 1
- data = length 24, hash ABE659B
- sample 137:
- time = 2740000
- flags = 1
- data = length 24, hash ED2691D2
- sample 138:
- time = 2760000
- flags = 1
- data = length 24, hash D998C80E
- sample 139:
- time = 2780000
- flags = 1
- data = length 24, hash 8DC0DF5C
- sample 140:
- time = 2800000
- flags = 1
- data = length 24, hash 7692247B
- sample 141:
- time = 2820000
- flags = 1
- data = length 24, hash C1D1CCB9
- sample 142:
- time = 2840000
- flags = 1
- data = length 24, hash 362CE78E
- sample 143:
- time = 2860000
- flags = 1
- data = length 24, hash 54FA84A
- sample 144:
- time = 2880000
- flags = 1
- data = length 24, hash 29E88C84
- sample 145:
- time = 2900000
- flags = 1
- data = length 24, hash 1CD848AC
- sample 146:
- time = 2920000
- flags = 1
- data = length 24, hash 5C3D4A79
- sample 147:
- time = 2940000
- flags = 1
- data = length 24, hash 1AA8E604
- sample 148:
- time = 2960000
- flags = 1
- data = length 24, hash 186A4316
- sample 149:
- time = 2980000
- flags = 1
- data = length 24, hash 61ACE481
- sample 150:
- time = 3000000
- flags = 1
- data = length 24, hash D0C42780
- sample 151:
- time = 3020000
- flags = 1
- data = length 24, hash FAD51BA1
- sample 152:
- time = 3040000
- flags = 1
- data = length 24, hash F1A9AC71
- sample 153:
- time = 3060000
- flags = 1
- data = length 24, hash 24425449
- sample 154:
- time = 3080000
- flags = 1
- data = length 24, hash 37AAC3E6
- sample 155:
- time = 3100000
- flags = 1
- data = length 24, hash 91F68CB4
- sample 156:
- time = 3120000
- flags = 1
- data = length 24, hash F8C92820
- sample 157:
- time = 3140000
- flags = 1
- data = length 24, hash ECD39C3E
- sample 158:
- time = 3160000
- flags = 1
- data = length 24, hash B27D8F78
- sample 159:
- time = 3180000
- flags = 1
- data = length 24, hash C9EB3DFB
- sample 160:
- time = 3200000
- flags = 1
- data = length 24, hash 88DC54A2
- sample 161:
- time = 3220000
- flags = 1
- data = length 24, hash 7FC4C5BE
- sample 162:
- time = 3240000
- flags = 1
- data = length 24, hash E4F684EF
- sample 163:
- time = 3260000
- flags = 1
- data = length 24, hash 55C08B56
- sample 164:
- time = 3280000
- flags = 1
- data = length 24, hash E5A0F006
- sample 165:
- time = 3300000
- flags = 1
- data = length 24, hash DE3F3AA7
- sample 166:
- time = 3320000
- flags = 1
- data = length 24, hash 3F28AE7F
- sample 167:
- time = 3340000
- flags = 1
- data = length 24, hash 3949CAFF
- sample 168:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.1.dump b/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.1.dump
deleted file mode 100644
index 1c2318d..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.1.dump
+++ /dev/null
@@ -1,483 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3380000
- getPosition(0) = [[timeUs=0, position=9]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2712
- sample count = 113
- sample 0:
- time = 1120000
- flags = 1
- data = length 24, hash EEB921BD
- sample 1:
- time = 1140000
- flags = 1
- data = length 24, hash 8B941A4C
- sample 2:
- time = 1160000
- flags = 1
- data = length 24, hash ED5F5FEE
- sample 3:
- time = 1180000
- flags = 1
- data = length 24, hash A588E0BB
- sample 4:
- time = 1200000
- flags = 1
- data = length 24, hash 588CBC01
- sample 5:
- time = 1220000
- flags = 1
- data = length 24, hash DE22266C
- sample 6:
- time = 1240000
- flags = 1
- data = length 24, hash 921B6E5C
- sample 7:
- time = 1260000
- flags = 1
- data = length 24, hash EC11F041
- sample 8:
- time = 1280000
- flags = 1
- data = length 24, hash 5BA9E0A3
- sample 9:
- time = 1300000
- flags = 1
- data = length 24, hash DB6D52F3
- sample 10:
- time = 1320000
- flags = 1
- data = length 24, hash 8EEBE525
- sample 11:
- time = 1340000
- flags = 1
- data = length 24, hash 47A742AE
- sample 12:
- time = 1360000
- flags = 1
- data = length 24, hash E93F1E03
- sample 13:
- time = 1380000
- flags = 1
- data = length 24, hash 3251F57C
- sample 14:
- time = 1400000
- flags = 1
- data = length 24, hash 3EDBBBDD
- sample 15:
- time = 1420000
- flags = 1
- data = length 24, hash 2E98465A
- sample 16:
- time = 1440000
- flags = 1
- data = length 24, hash A09EA52E
- sample 17:
- time = 1460000
- flags = 1
- data = length 24, hash A2A86FA6
- sample 18:
- time = 1480000
- flags = 1
- data = length 24, hash 71DCD51C
- sample 19:
- time = 1500000
- flags = 1
- data = length 24, hash 2B02DEE1
- sample 20:
- time = 1520000
- flags = 1
- data = length 24, hash 7A725192
- sample 21:
- time = 1540000
- flags = 1
- data = length 24, hash 929AD483
- sample 22:
- time = 1560000
- flags = 1
- data = length 24, hash 68440BF5
- sample 23:
- time = 1580000
- flags = 1
- data = length 24, hash 5BD41AD6
- sample 24:
- time = 1600000
- flags = 1
- data = length 24, hash 91A381
- sample 25:
- time = 1620000
- flags = 1
- data = length 24, hash 8010C408
- sample 26:
- time = 1640000
- flags = 1
- data = length 24, hash 482274BE
- sample 27:
- time = 1660000
- flags = 1
- data = length 24, hash D7DB8BCC
- sample 28:
- time = 1680000
- flags = 1
- data = length 24, hash 680BD9DD
- sample 29:
- time = 1700000
- flags = 1
- data = length 24, hash E313577C
- sample 30:
- time = 1720000
- flags = 1
- data = length 24, hash 9C10B0CD
- sample 31:
- time = 1740000
- flags = 1
- data = length 24, hash 2D90AC02
- sample 32:
- time = 1760000
- flags = 1
- data = length 24, hash 64E8C245
- sample 33:
- time = 1780000
- flags = 1
- data = length 24, hash 3954AC1B
- sample 34:
- time = 1800000
- flags = 1
- data = length 24, hash ACB8999F
- sample 35:
- time = 1820000
- flags = 1
- data = length 24, hash 43AE3957
- sample 36:
- time = 1840000
- flags = 1
- data = length 24, hash 3C664DB7
- sample 37:
- time = 1860000
- flags = 1
- data = length 24, hash 9354B576
- sample 38:
- time = 1880000
- flags = 1
- data = length 24, hash B5B9C14E
- sample 39:
- time = 1900000
- flags = 1
- data = length 24, hash 7DA9C98F
- sample 40:
- time = 1920000
- flags = 1
- data = length 24, hash EFEE54C6
- sample 41:
- time = 1940000
- flags = 1
- data = length 24, hash 79DC8CBD
- sample 42:
- time = 1960000
- flags = 1
- data = length 24, hash A71A475C
- sample 43:
- time = 1980000
- flags = 1
- data = length 24, hash CA1CBB94
- sample 44:
- time = 2000000
- flags = 1
- data = length 24, hash 91922226
- sample 45:
- time = 2020000
- flags = 1
- data = length 24, hash C90278BC
- sample 46:
- time = 2040000
- flags = 1
- data = length 24, hash BD51986F
- sample 47:
- time = 2060000
- flags = 1
- data = length 24, hash 90AEF368
- sample 48:
- time = 2080000
- flags = 1
- data = length 24, hash 1D83C955
- sample 49:
- time = 2100000
- flags = 1
- data = length 24, hash 8FA9A915
- sample 50:
- time = 2120000
- flags = 1
- data = length 24, hash C6C753E0
- sample 51:
- time = 2140000
- flags = 1
- data = length 24, hash 85FA27A7
- sample 52:
- time = 2160000
- flags = 1
- data = length 24, hash A0277324
- sample 53:
- time = 2180000
- flags = 1
- data = length 24, hash B7696535
- sample 54:
- time = 2200000
- flags = 1
- data = length 24, hash D69D668C
- sample 55:
- time = 2220000
- flags = 1
- data = length 24, hash 34C057CD
- sample 56:
- time = 2240000
- flags = 1
- data = length 24, hash 4EC5E974
- sample 57:
- time = 2260000
- flags = 1
- data = length 24, hash 1C1CD40D
- sample 58:
- time = 2280000
- flags = 1
- data = length 24, hash 76CC54BC
- sample 59:
- time = 2300000
- flags = 1
- data = length 24, hash D497ACF5
- sample 60:
- time = 2320000
- flags = 1
- data = length 24, hash A1386080
- sample 61:
- time = 2340000
- flags = 1
- data = length 24, hash 7ED36954
- sample 62:
- time = 2360000
- flags = 1
- data = length 24, hash C11A3BF9
- sample 63:
- time = 2380000
- flags = 1
- data = length 24, hash 8FB69488
- sample 64:
- time = 2400000
- flags = 1
- data = length 24, hash C6225F59
- sample 65:
- time = 2420000
- flags = 1
- data = length 24, hash 122AB6D2
- sample 66:
- time = 2440000
- flags = 1
- data = length 24, hash 1E195E7D
- sample 67:
- time = 2460000
- flags = 1
- data = length 24, hash BD3DF418
- sample 68:
- time = 2480000
- flags = 1
- data = length 24, hash D8AE4A5
- sample 69:
- time = 2500000
- flags = 1
- data = length 24, hash 977BD182
- sample 70:
- time = 2520000
- flags = 1
- data = length 24, hash F361F060
- sample 71:
- time = 2540000
- flags = 1
- data = length 24, hash 11EC8CD0
- sample 72:
- time = 2560000
- flags = 1
- data = length 24, hash 3798F3D2
- sample 73:
- time = 2580000
- flags = 1
- data = length 24, hash B2C2517C
- sample 74:
- time = 2600000
- flags = 1
- data = length 24, hash FBE0D0D8
- sample 75:
- time = 2620000
- flags = 1
- data = length 24, hash 7033172F
- sample 76:
- time = 2640000
- flags = 1
- data = length 24, hash BE760029
- sample 77:
- time = 2660000
- flags = 1
- data = length 24, hash 590AF28C
- sample 78:
- time = 2680000
- flags = 1
- data = length 24, hash AD28C48F
- sample 79:
- time = 2700000
- flags = 1
- data = length 24, hash 640AA61B
- sample 80:
- time = 2720000
- flags = 1
- data = length 24, hash ABE659B
- sample 81:
- time = 2740000
- flags = 1
- data = length 24, hash ED2691D2
- sample 82:
- time = 2760000
- flags = 1
- data = length 24, hash D998C80E
- sample 83:
- time = 2780000
- flags = 1
- data = length 24, hash 8DC0DF5C
- sample 84:
- time = 2800000
- flags = 1
- data = length 24, hash 7692247B
- sample 85:
- time = 2820000
- flags = 1
- data = length 24, hash C1D1CCB9
- sample 86:
- time = 2840000
- flags = 1
- data = length 24, hash 362CE78E
- sample 87:
- time = 2860000
- flags = 1
- data = length 24, hash 54FA84A
- sample 88:
- time = 2880000
- flags = 1
- data = length 24, hash 29E88C84
- sample 89:
- time = 2900000
- flags = 1
- data = length 24, hash 1CD848AC
- sample 90:
- time = 2920000
- flags = 1
- data = length 24, hash 5C3D4A79
- sample 91:
- time = 2940000
- flags = 1
- data = length 24, hash 1AA8E604
- sample 92:
- time = 2960000
- flags = 1
- data = length 24, hash 186A4316
- sample 93:
- time = 2980000
- flags = 1
- data = length 24, hash 61ACE481
- sample 94:
- time = 3000000
- flags = 1
- data = length 24, hash D0C42780
- sample 95:
- time = 3020000
- flags = 1
- data = length 24, hash FAD51BA1
- sample 96:
- time = 3040000
- flags = 1
- data = length 24, hash F1A9AC71
- sample 97:
- time = 3060000
- flags = 1
- data = length 24, hash 24425449
- sample 98:
- time = 3080000
- flags = 1
- data = length 24, hash 37AAC3E6
- sample 99:
- time = 3100000
- flags = 1
- data = length 24, hash 91F68CB4
- sample 100:
- time = 3120000
- flags = 1
- data = length 24, hash F8C92820
- sample 101:
- time = 3140000
- flags = 1
- data = length 24, hash ECD39C3E
- sample 102:
- time = 3160000
- flags = 1
- data = length 24, hash B27D8F78
- sample 103:
- time = 3180000
- flags = 1
- data = length 24, hash C9EB3DFB
- sample 104:
- time = 3200000
- flags = 1
- data = length 24, hash 88DC54A2
- sample 105:
- time = 3220000
- flags = 1
- data = length 24, hash 7FC4C5BE
- sample 106:
- time = 3240000
- flags = 1
- data = length 24, hash E4F684EF
- sample 107:
- time = 3260000
- flags = 1
- data = length 24, hash 55C08B56
- sample 108:
- time = 3280000
- flags = 1
- data = length 24, hash E5A0F006
- sample 109:
- time = 3300000
- flags = 1
- data = length 24, hash DE3F3AA7
- sample 110:
- time = 3320000
- flags = 1
- data = length 24, hash 3F28AE7F
- sample 111:
- time = 3340000
- flags = 1
- data = length 24, hash 3949CAFF
- sample 112:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.2.dump b/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.2.dump
deleted file mode 100644
index 8489ad9..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.2.dump
+++ /dev/null
@@ -1,259 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3380000
- getPosition(0) = [[timeUs=0, position=9]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 1368
- sample count = 57
- sample 0:
- time = 2240000
- flags = 1
- data = length 24, hash 4EC5E974
- sample 1:
- time = 2260000
- flags = 1
- data = length 24, hash 1C1CD40D
- sample 2:
- time = 2280000
- flags = 1
- data = length 24, hash 76CC54BC
- sample 3:
- time = 2300000
- flags = 1
- data = length 24, hash D497ACF5
- sample 4:
- time = 2320000
- flags = 1
- data = length 24, hash A1386080
- sample 5:
- time = 2340000
- flags = 1
- data = length 24, hash 7ED36954
- sample 6:
- time = 2360000
- flags = 1
- data = length 24, hash C11A3BF9
- sample 7:
- time = 2380000
- flags = 1
- data = length 24, hash 8FB69488
- sample 8:
- time = 2400000
- flags = 1
- data = length 24, hash C6225F59
- sample 9:
- time = 2420000
- flags = 1
- data = length 24, hash 122AB6D2
- sample 10:
- time = 2440000
- flags = 1
- data = length 24, hash 1E195E7D
- sample 11:
- time = 2460000
- flags = 1
- data = length 24, hash BD3DF418
- sample 12:
- time = 2480000
- flags = 1
- data = length 24, hash D8AE4A5
- sample 13:
- time = 2500000
- flags = 1
- data = length 24, hash 977BD182
- sample 14:
- time = 2520000
- flags = 1
- data = length 24, hash F361F060
- sample 15:
- time = 2540000
- flags = 1
- data = length 24, hash 11EC8CD0
- sample 16:
- time = 2560000
- flags = 1
- data = length 24, hash 3798F3D2
- sample 17:
- time = 2580000
- flags = 1
- data = length 24, hash B2C2517C
- sample 18:
- time = 2600000
- flags = 1
- data = length 24, hash FBE0D0D8
- sample 19:
- time = 2620000
- flags = 1
- data = length 24, hash 7033172F
- sample 20:
- time = 2640000
- flags = 1
- data = length 24, hash BE760029
- sample 21:
- time = 2660000
- flags = 1
- data = length 24, hash 590AF28C
- sample 22:
- time = 2680000
- flags = 1
- data = length 24, hash AD28C48F
- sample 23:
- time = 2700000
- flags = 1
- data = length 24, hash 640AA61B
- sample 24:
- time = 2720000
- flags = 1
- data = length 24, hash ABE659B
- sample 25:
- time = 2740000
- flags = 1
- data = length 24, hash ED2691D2
- sample 26:
- time = 2760000
- flags = 1
- data = length 24, hash D998C80E
- sample 27:
- time = 2780000
- flags = 1
- data = length 24, hash 8DC0DF5C
- sample 28:
- time = 2800000
- flags = 1
- data = length 24, hash 7692247B
- sample 29:
- time = 2820000
- flags = 1
- data = length 24, hash C1D1CCB9
- sample 30:
- time = 2840000
- flags = 1
- data = length 24, hash 362CE78E
- sample 31:
- time = 2860000
- flags = 1
- data = length 24, hash 54FA84A
- sample 32:
- time = 2880000
- flags = 1
- data = length 24, hash 29E88C84
- sample 33:
- time = 2900000
- flags = 1
- data = length 24, hash 1CD848AC
- sample 34:
- time = 2920000
- flags = 1
- data = length 24, hash 5C3D4A79
- sample 35:
- time = 2940000
- flags = 1
- data = length 24, hash 1AA8E604
- sample 36:
- time = 2960000
- flags = 1
- data = length 24, hash 186A4316
- sample 37:
- time = 2980000
- flags = 1
- data = length 24, hash 61ACE481
- sample 38:
- time = 3000000
- flags = 1
- data = length 24, hash D0C42780
- sample 39:
- time = 3020000
- flags = 1
- data = length 24, hash FAD51BA1
- sample 40:
- time = 3040000
- flags = 1
- data = length 24, hash F1A9AC71
- sample 41:
- time = 3060000
- flags = 1
- data = length 24, hash 24425449
- sample 42:
- time = 3080000
- flags = 1
- data = length 24, hash 37AAC3E6
- sample 43:
- time = 3100000
- flags = 1
- data = length 24, hash 91F68CB4
- sample 44:
- time = 3120000
- flags = 1
- data = length 24, hash F8C92820
- sample 45:
- time = 3140000
- flags = 1
- data = length 24, hash ECD39C3E
- sample 46:
- time = 3160000
- flags = 1
- data = length 24, hash B27D8F78
- sample 47:
- time = 3180000
- flags = 1
- data = length 24, hash C9EB3DFB
- sample 48:
- time = 3200000
- flags = 1
- data = length 24, hash 88DC54A2
- sample 49:
- time = 3220000
- flags = 1
- data = length 24, hash 7FC4C5BE
- sample 50:
- time = 3240000
- flags = 1
- data = length 24, hash E4F684EF
- sample 51:
- time = 3260000
- flags = 1
- data = length 24, hash 55C08B56
- sample 52:
- time = 3280000
- flags = 1
- data = length 24, hash E5A0F006
- sample 53:
- time = 3300000
- flags = 1
- data = length 24, hash DE3F3AA7
- sample 54:
- time = 3320000
- flags = 1
- data = length 24, hash 3F28AE7F
- sample 55:
- time = 3340000
- flags = 1
- data = length 24, hash 3949CAFF
- sample 56:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.3.dump b/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.3.dump
deleted file mode 100644
index e18a4ad..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3380000
- getPosition(0) = [[timeUs=0, position=9]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 24
- sample count = 1
- sample 0:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.unklen.dump b/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.unklen.dump
deleted file mode 100644
index c744d9b..0000000
--- a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr.unklen.dump
+++ /dev/null
@@ -1,707 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/amr-wb
- maxInputSize = 61
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 16000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 4056
- sample count = 169
- sample 0:
- time = 0
- flags = 1
- data = length 24, hash C3025798
- sample 1:
- time = 20000
- flags = 1
- data = length 24, hash 39CABAE9
- sample 2:
- time = 40000
- flags = 1
- data = length 24, hash 2752F470
- sample 3:
- time = 60000
- flags = 1
- data = length 24, hash 394F76F6
- sample 4:
- time = 80000
- flags = 1
- data = length 24, hash FF9EEF
- sample 5:
- time = 100000
- flags = 1
- data = length 24, hash 54ECB1B4
- sample 6:
- time = 120000
- flags = 1
- data = length 24, hash 6D7A3A5F
- sample 7:
- time = 140000
- flags = 1
- data = length 24, hash 684CD144
- sample 8:
- time = 160000
- flags = 1
- data = length 24, hash 87B7D176
- sample 9:
- time = 180000
- flags = 1
- data = length 24, hash 4C02F9A5
- sample 10:
- time = 200000
- flags = 1
- data = length 24, hash B4154108
- sample 11:
- time = 220000
- flags = 1
- data = length 24, hash 4448F477
- sample 12:
- time = 240000
- flags = 1
- data = length 24, hash 755A4939
- sample 13:
- time = 260000
- flags = 1
- data = length 24, hash 8C8BC6C3
- sample 14:
- time = 280000
- flags = 1
- data = length 24, hash BC37F63F
- sample 15:
- time = 300000
- flags = 1
- data = length 24, hash 3352C43C
- sample 16:
- time = 320000
- flags = 1
- data = length 24, hash 7998E1F2
- sample 17:
- time = 340000
- flags = 1
- data = length 24, hash A8ECBEFC
- sample 18:
- time = 360000
- flags = 1
- data = length 24, hash 944AC118
- sample 19:
- time = 380000
- flags = 1
- data = length 24, hash FD2C8E1F
- sample 20:
- time = 400000
- flags = 1
- data = length 24, hash B3D867AF
- sample 21:
- time = 420000
- flags = 1
- data = length 24, hash 3DC6E592
- sample 22:
- time = 440000
- flags = 1
- data = length 24, hash 32B276CD
- sample 23:
- time = 460000
- flags = 1
- data = length 24, hash 5488AEF3
- sample 24:
- time = 480000
- flags = 1
- data = length 24, hash 7A4D516
- sample 25:
- time = 500000
- flags = 1
- data = length 24, hash 570AE83F
- sample 26:
- time = 520000
- flags = 1
- data = length 24, hash E5CB3477
- sample 27:
- time = 540000
- flags = 1
- data = length 24, hash E04C00E4
- sample 28:
- time = 560000
- flags = 1
- data = length 24, hash 21B7C97
- sample 29:
- time = 580000
- flags = 1
- data = length 24, hash 1633F470
- sample 30:
- time = 600000
- flags = 1
- data = length 24, hash 28D65CA6
- sample 31:
- time = 620000
- flags = 1
- data = length 24, hash CC6A675C
- sample 32:
- time = 640000
- flags = 1
- data = length 24, hash 4C91080A
- sample 33:
- time = 660000
- flags = 1
- data = length 24, hash F6482FB5
- sample 34:
- time = 680000
- flags = 1
- data = length 24, hash 2C76F48C
- sample 35:
- time = 700000
- flags = 1
- data = length 24, hash 6E3B0D72
- sample 36:
- time = 720000
- flags = 1
- data = length 24, hash 799AA003
- sample 37:
- time = 740000
- flags = 1
- data = length 24, hash DFC0BA81
- sample 38:
- time = 760000
- flags = 1
- data = length 24, hash CBDF3826
- sample 39:
- time = 780000
- flags = 1
- data = length 24, hash 16862B75
- sample 40:
- time = 800000
- flags = 1
- data = length 24, hash 865A828E
- sample 41:
- time = 820000
- flags = 1
- data = length 24, hash 336BBDC9
- sample 42:
- time = 840000
- flags = 1
- data = length 24, hash 6CFC6C34
- sample 43:
- time = 860000
- flags = 1
- data = length 24, hash 32C8CD46
- sample 44:
- time = 880000
- flags = 1
- data = length 24, hash 9FE11C4C
- sample 45:
- time = 900000
- flags = 1
- data = length 24, hash AA5A12B7
- sample 46:
- time = 920000
- flags = 1
- data = length 24, hash AA0F4A4D
- sample 47:
- time = 940000
- flags = 1
- data = length 24, hash 34415484
- sample 48:
- time = 960000
- flags = 1
- data = length 24, hash 5018928E
- sample 49:
- time = 980000
- flags = 1
- data = length 24, hash 4A04D162
- sample 50:
- time = 1000000
- flags = 1
- data = length 24, hash 4C70F9F0
- sample 51:
- time = 1020000
- flags = 1
- data = length 24, hash 99EF3168
- sample 52:
- time = 1040000
- flags = 1
- data = length 24, hash C600DAF
- sample 53:
- time = 1060000
- flags = 1
- data = length 24, hash FDBB192E
- sample 54:
- time = 1080000
- flags = 1
- data = length 24, hash 99096A48
- sample 55:
- time = 1100000
- flags = 1
- data = length 24, hash D793F88B
- sample 56:
- time = 1120000
- flags = 1
- data = length 24, hash EEB921BD
- sample 57:
- time = 1140000
- flags = 1
- data = length 24, hash 8B941A4C
- sample 58:
- time = 1160000
- flags = 1
- data = length 24, hash ED5F5FEE
- sample 59:
- time = 1180000
- flags = 1
- data = length 24, hash A588E0BB
- sample 60:
- time = 1200000
- flags = 1
- data = length 24, hash 588CBC01
- sample 61:
- time = 1220000
- flags = 1
- data = length 24, hash DE22266C
- sample 62:
- time = 1240000
- flags = 1
- data = length 24, hash 921B6E5C
- sample 63:
- time = 1260000
- flags = 1
- data = length 24, hash EC11F041
- sample 64:
- time = 1280000
- flags = 1
- data = length 24, hash 5BA9E0A3
- sample 65:
- time = 1300000
- flags = 1
- data = length 24, hash DB6D52F3
- sample 66:
- time = 1320000
- flags = 1
- data = length 24, hash 8EEBE525
- sample 67:
- time = 1340000
- flags = 1
- data = length 24, hash 47A742AE
- sample 68:
- time = 1360000
- flags = 1
- data = length 24, hash E93F1E03
- sample 69:
- time = 1380000
- flags = 1
- data = length 24, hash 3251F57C
- sample 70:
- time = 1400000
- flags = 1
- data = length 24, hash 3EDBBBDD
- sample 71:
- time = 1420000
- flags = 1
- data = length 24, hash 2E98465A
- sample 72:
- time = 1440000
- flags = 1
- data = length 24, hash A09EA52E
- sample 73:
- time = 1460000
- flags = 1
- data = length 24, hash A2A86FA6
- sample 74:
- time = 1480000
- flags = 1
- data = length 24, hash 71DCD51C
- sample 75:
- time = 1500000
- flags = 1
- data = length 24, hash 2B02DEE1
- sample 76:
- time = 1520000
- flags = 1
- data = length 24, hash 7A725192
- sample 77:
- time = 1540000
- flags = 1
- data = length 24, hash 929AD483
- sample 78:
- time = 1560000
- flags = 1
- data = length 24, hash 68440BF5
- sample 79:
- time = 1580000
- flags = 1
- data = length 24, hash 5BD41AD6
- sample 80:
- time = 1600000
- flags = 1
- data = length 24, hash 91A381
- sample 81:
- time = 1620000
- flags = 1
- data = length 24, hash 8010C408
- sample 82:
- time = 1640000
- flags = 1
- data = length 24, hash 482274BE
- sample 83:
- time = 1660000
- flags = 1
- data = length 24, hash D7DB8BCC
- sample 84:
- time = 1680000
- flags = 1
- data = length 24, hash 680BD9DD
- sample 85:
- time = 1700000
- flags = 1
- data = length 24, hash E313577C
- sample 86:
- time = 1720000
- flags = 1
- data = length 24, hash 9C10B0CD
- sample 87:
- time = 1740000
- flags = 1
- data = length 24, hash 2D90AC02
- sample 88:
- time = 1760000
- flags = 1
- data = length 24, hash 64E8C245
- sample 89:
- time = 1780000
- flags = 1
- data = length 24, hash 3954AC1B
- sample 90:
- time = 1800000
- flags = 1
- data = length 24, hash ACB8999F
- sample 91:
- time = 1820000
- flags = 1
- data = length 24, hash 43AE3957
- sample 92:
- time = 1840000
- flags = 1
- data = length 24, hash 3C664DB7
- sample 93:
- time = 1860000
- flags = 1
- data = length 24, hash 9354B576
- sample 94:
- time = 1880000
- flags = 1
- data = length 24, hash B5B9C14E
- sample 95:
- time = 1900000
- flags = 1
- data = length 24, hash 7DA9C98F
- sample 96:
- time = 1920000
- flags = 1
- data = length 24, hash EFEE54C6
- sample 97:
- time = 1940000
- flags = 1
- data = length 24, hash 79DC8CBD
- sample 98:
- time = 1960000
- flags = 1
- data = length 24, hash A71A475C
- sample 99:
- time = 1980000
- flags = 1
- data = length 24, hash CA1CBB94
- sample 100:
- time = 2000000
- flags = 1
- data = length 24, hash 91922226
- sample 101:
- time = 2020000
- flags = 1
- data = length 24, hash C90278BC
- sample 102:
- time = 2040000
- flags = 1
- data = length 24, hash BD51986F
- sample 103:
- time = 2060000
- flags = 1
- data = length 24, hash 90AEF368
- sample 104:
- time = 2080000
- flags = 1
- data = length 24, hash 1D83C955
- sample 105:
- time = 2100000
- flags = 1
- data = length 24, hash 8FA9A915
- sample 106:
- time = 2120000
- flags = 1
- data = length 24, hash C6C753E0
- sample 107:
- time = 2140000
- flags = 1
- data = length 24, hash 85FA27A7
- sample 108:
- time = 2160000
- flags = 1
- data = length 24, hash A0277324
- sample 109:
- time = 2180000
- flags = 1
- data = length 24, hash B7696535
- sample 110:
- time = 2200000
- flags = 1
- data = length 24, hash D69D668C
- sample 111:
- time = 2220000
- flags = 1
- data = length 24, hash 34C057CD
- sample 112:
- time = 2240000
- flags = 1
- data = length 24, hash 4EC5E974
- sample 113:
- time = 2260000
- flags = 1
- data = length 24, hash 1C1CD40D
- sample 114:
- time = 2280000
- flags = 1
- data = length 24, hash 76CC54BC
- sample 115:
- time = 2300000
- flags = 1
- data = length 24, hash D497ACF5
- sample 116:
- time = 2320000
- flags = 1
- data = length 24, hash A1386080
- sample 117:
- time = 2340000
- flags = 1
- data = length 24, hash 7ED36954
- sample 118:
- time = 2360000
- flags = 1
- data = length 24, hash C11A3BF9
- sample 119:
- time = 2380000
- flags = 1
- data = length 24, hash 8FB69488
- sample 120:
- time = 2400000
- flags = 1
- data = length 24, hash C6225F59
- sample 121:
- time = 2420000
- flags = 1
- data = length 24, hash 122AB6D2
- sample 122:
- time = 2440000
- flags = 1
- data = length 24, hash 1E195E7D
- sample 123:
- time = 2460000
- flags = 1
- data = length 24, hash BD3DF418
- sample 124:
- time = 2480000
- flags = 1
- data = length 24, hash D8AE4A5
- sample 125:
- time = 2500000
- flags = 1
- data = length 24, hash 977BD182
- sample 126:
- time = 2520000
- flags = 1
- data = length 24, hash F361F060
- sample 127:
- time = 2540000
- flags = 1
- data = length 24, hash 11EC8CD0
- sample 128:
- time = 2560000
- flags = 1
- data = length 24, hash 3798F3D2
- sample 129:
- time = 2580000
- flags = 1
- data = length 24, hash B2C2517C
- sample 130:
- time = 2600000
- flags = 1
- data = length 24, hash FBE0D0D8
- sample 131:
- time = 2620000
- flags = 1
- data = length 24, hash 7033172F
- sample 132:
- time = 2640000
- flags = 1
- data = length 24, hash BE760029
- sample 133:
- time = 2660000
- flags = 1
- data = length 24, hash 590AF28C
- sample 134:
- time = 2680000
- flags = 1
- data = length 24, hash AD28C48F
- sample 135:
- time = 2700000
- flags = 1
- data = length 24, hash 640AA61B
- sample 136:
- time = 2720000
- flags = 1
- data = length 24, hash ABE659B
- sample 137:
- time = 2740000
- flags = 1
- data = length 24, hash ED2691D2
- sample 138:
- time = 2760000
- flags = 1
- data = length 24, hash D998C80E
- sample 139:
- time = 2780000
- flags = 1
- data = length 24, hash 8DC0DF5C
- sample 140:
- time = 2800000
- flags = 1
- data = length 24, hash 7692247B
- sample 141:
- time = 2820000
- flags = 1
- data = length 24, hash C1D1CCB9
- sample 142:
- time = 2840000
- flags = 1
- data = length 24, hash 362CE78E
- sample 143:
- time = 2860000
- flags = 1
- data = length 24, hash 54FA84A
- sample 144:
- time = 2880000
- flags = 1
- data = length 24, hash 29E88C84
- sample 145:
- time = 2900000
- flags = 1
- data = length 24, hash 1CD848AC
- sample 146:
- time = 2920000
- flags = 1
- data = length 24, hash 5C3D4A79
- sample 147:
- time = 2940000
- flags = 1
- data = length 24, hash 1AA8E604
- sample 148:
- time = 2960000
- flags = 1
- data = length 24, hash 186A4316
- sample 149:
- time = 2980000
- flags = 1
- data = length 24, hash 61ACE481
- sample 150:
- time = 3000000
- flags = 1
- data = length 24, hash D0C42780
- sample 151:
- time = 3020000
- flags = 1
- data = length 24, hash FAD51BA1
- sample 152:
- time = 3040000
- flags = 1
- data = length 24, hash F1A9AC71
- sample 153:
- time = 3060000
- flags = 1
- data = length 24, hash 24425449
- sample 154:
- time = 3080000
- flags = 1
- data = length 24, hash 37AAC3E6
- sample 155:
- time = 3100000
- flags = 1
- data = length 24, hash 91F68CB4
- sample 156:
- time = 3120000
- flags = 1
- data = length 24, hash F8C92820
- sample 157:
- time = 3140000
- flags = 1
- data = length 24, hash ECD39C3E
- sample 158:
- time = 3160000
- flags = 1
- data = length 24, hash B27D8F78
- sample 159:
- time = 3180000
- flags = 1
- data = length 24, hash C9EB3DFB
- sample 160:
- time = 3200000
- flags = 1
- data = length 24, hash 88DC54A2
- sample 161:
- time = 3220000
- flags = 1
- data = length 24, hash 7FC4C5BE
- sample 162:
- time = 3240000
- flags = 1
- data = length 24, hash E4F684EF
- sample 163:
- time = 3260000
- flags = 1
- data = length 24, hash 55C08B56
- sample 164:
- time = 3280000
- flags = 1
- data = length 24, hash E5A0F006
- sample 165:
- time = 3300000
- flags = 1
- data = length 24, hash DE3F3AA7
- sample 166:
- time = 3320000
- flags = 1
- data = length 24, hash 3F28AE7F
- sample 167:
- time = 3340000
- flags = 1
- data = length 24, hash 3949CAFF
- sample 168:
- time = 3360000
- flags = 1
- data = length 24, hash 772665A0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear.flac.0.dump
deleted file mode 100644
index abc362d..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear.flac.1.dump
deleted file mode 100644
index 2ad83e6..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear.flac.2.dump
deleted file mode 100644
index fc695d0..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear.flac.3.dump
deleted file mode 100644
index 7553b6b..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.0.dump
deleted file mode 100644
index cac4452..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 9218FDB7
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.1.dump
deleted file mode 100644
index 39055fb..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 9218FDB7
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.2.dump
deleted file mode 100644
index 14fdead..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 9218FDB7
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.3.dump
deleted file mode 100644
index b27b4b7..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 9218FDB7
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_num_samples.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_no_num_samples.flac.0.dump
deleted file mode 100644
index 0a1736f..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_num_samples.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 49FA2C21
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac.0.dump
deleted file mode 100644
index 45b7539..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 49FA2C21
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.0.dump
deleted file mode 100644
index 14986d5..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=42]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.1.dump
deleted file mode 100644
index 160edcb..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=42]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.2.dump
deleted file mode 100644
index 7580b24..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=42]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.3.dump
deleted file mode 100644
index 7608b4e..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.3.dump
+++ /dev/null
@@ -1,36 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=42]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 445
- sample count = 1
- sample 0:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.unklen.dump b/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.unklen.dump
deleted file mode 100644
index bd8f182..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac.unklen.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = false
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.0.dump
deleted file mode 100644
index 932cb8d..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.0.dump
+++ /dev/null
@@ -1,140 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8230]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1408000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 6456
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 7249A1B8
- total output bytes = 144086
- sample count = 27
- sample 0:
- time = 0
- flags = 1
- data = length 5415, hash 915DBC66
- sample 1:
- time = 104727
- flags = 1
- data = length 5529, hash EFD564F7
- sample 2:
- time = 209454
- flags = 1
- data = length 5480, hash ADA922FB
- sample 3:
- time = 314181
- flags = 1
- data = length 5290, hash 7BCEA5FC
- sample 4:
- time = 418909
- flags = 1
- data = length 5579, hash DBB36F37
- sample 5:
- time = 523636
- flags = 1
- data = length 5423, hash AB53F799
- sample 6:
- time = 628363
- flags = 1
- data = length 5583, hash 7243C284
- sample 7:
- time = 733090
- flags = 1
- data = length 5547, hash 9DA9C99E
- sample 8:
- time = 837818
- flags = 1
- data = length 5414, hash 90768345
- sample 9:
- time = 942545
- flags = 1
- data = length 5531, hash 1CD2FF67
- sample 10:
- time = 1047272
- flags = 1
- data = length 5870, hash A9A5CAEE
- sample 11:
- time = 1152000
- flags = 1
- data = length 5667, hash 875566A1
- sample 12:
- time = 1256727
- flags = 1
- data = length 5614, hash 5573694C
- sample 13:
- time = 1361454
- flags = 1
- data = length 6456, hash 921F3DE7
- sample 14:
- time = 1466181
- flags = 1
- data = length 5817, hash EBECBD16
- sample 15:
- time = 1570909
- flags = 1
- data = length 5751, hash 4A7D4C6B
- sample 16:
- time = 1675636
- flags = 1
- data = length 5620, hash B78F8E8D
- sample 17:
- time = 1780363
- flags = 1
- data = length 5535, hash 8187C107
- sample 18:
- time = 1885090
- flags = 1
- data = length 5517, hash 79FF36CB
- sample 19:
- time = 1989818
- flags = 1
- data = length 5716, hash 349FC281
- sample 20:
- time = 2094545
- flags = 1
- data = length 5556, hash BE97B2CA
- sample 21:
- time = 2199272
- flags = 1
- data = length 5703, hash 531F9FE3
- sample 22:
- time = 2304000
- flags = 1
- data = length 5652, hash 1277485D
- sample 23:
- time = 2408727
- flags = 1
- data = length 5607, hash 14862CB6
- sample 24:
- time = 2513454
- flags = 1
- data = length 5829, hash FCAF2F1C
- sample 25:
- time = 2618181
- flags = 1
- data = length 2837, hash 10F1716E
- sample 26:
- time = 2722909
- flags = 1
- data = length 548, hash B46F603C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.1.dump
deleted file mode 100644
index 7b5cc6c..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.1.dump
+++ /dev/null
@@ -1,108 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8230]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1408000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 6456
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 7249A1B8
- total output bytes = 100240
- sample count = 19
- sample 0:
- time = 837818
- flags = 1
- data = length 5414, hash 90768345
- sample 1:
- time = 942545
- flags = 1
- data = length 5531, hash 1CD2FF67
- sample 2:
- time = 1047272
- flags = 1
- data = length 5870, hash A9A5CAEE
- sample 3:
- time = 1152000
- flags = 1
- data = length 5667, hash 875566A1
- sample 4:
- time = 1256727
- flags = 1
- data = length 5614, hash 5573694C
- sample 5:
- time = 1361454
- flags = 1
- data = length 6456, hash 921F3DE7
- sample 6:
- time = 1466181
- flags = 1
- data = length 5817, hash EBECBD16
- sample 7:
- time = 1570909
- flags = 1
- data = length 5751, hash 4A7D4C6B
- sample 8:
- time = 1675636
- flags = 1
- data = length 5620, hash B78F8E8D
- sample 9:
- time = 1780363
- flags = 1
- data = length 5535, hash 8187C107
- sample 10:
- time = 1885090
- flags = 1
- data = length 5517, hash 79FF36CB
- sample 11:
- time = 1989818
- flags = 1
- data = length 5716, hash 349FC281
- sample 12:
- time = 2094545
- flags = 1
- data = length 5556, hash BE97B2CA
- sample 13:
- time = 2199272
- flags = 1
- data = length 5703, hash 531F9FE3
- sample 14:
- time = 2304000
- flags = 1
- data = length 5652, hash 1277485D
- sample 15:
- time = 2408727
- flags = 1
- data = length 5607, hash 14862CB6
- sample 16:
- time = 2513454
- flags = 1
- data = length 5829, hash FCAF2F1C
- sample 17:
- time = 2618181
- flags = 1
- data = length 2837, hash 10F1716E
- sample 18:
- time = 2722909
- flags = 1
- data = length 548, hash B46F603C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.2.dump
deleted file mode 100644
index 4e2d478..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.2.dump
+++ /dev/null
@@ -1,72 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8230]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1408000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 6456
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 7249A1B8
- total output bytes = 48500
- sample count = 10
- sample 0:
- time = 1780363
- flags = 1
- data = length 5535, hash 8187C107
- sample 1:
- time = 1885090
- flags = 1
- data = length 5517, hash 79FF36CB
- sample 2:
- time = 1989818
- flags = 1
- data = length 5716, hash 349FC281
- sample 3:
- time = 2094545
- flags = 1
- data = length 5556, hash BE97B2CA
- sample 4:
- time = 2199272
- flags = 1
- data = length 5703, hash 531F9FE3
- sample 5:
- time = 2304000
- flags = 1
- data = length 5652, hash 1277485D
- sample 6:
- time = 2408727
- flags = 1
- data = length 5607, hash 14862CB6
- sample 7:
- time = 2513454
- flags = 1
- data = length 5829, hash FCAF2F1C
- sample 8:
- time = 2618181
- flags = 1
- data = length 2837, hash 10F1716E
- sample 9:
- time = 2722909
- flags = 1
- data = length 548, hash B46F603C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.3.dump
deleted file mode 100644
index 3870f8d..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8230]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1408000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 6456
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 7249A1B8
- total output bytes = 3385
- sample count = 2
- sample 0:
- time = 2618181
- flags = 1
- data = length 2837, hash 10F1716E
- sample 1:
- time = 2722909
- flags = 1
- data = length 548, hash B46F603C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.0.dump
deleted file mode 100644
index ca1379b..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.1.dump
deleted file mode 100644
index aae190a..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.2.dump
deleted file mode 100644
index 7ab3784..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.3.dump
deleted file mode 100644
index 4fd2cb3..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac b/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac
deleted file mode 100644
index 3cfa81e..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac
+++ /dev/null
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.0.dump
deleted file mode 100644
index 3cb3265..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.1.dump
deleted file mode 100644
index 811aa65..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.2.dump
deleted file mode 100644
index 7eebfe2..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.3.dump
deleted file mode 100644
index 51c667d..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_id3_enabled.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=55284]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.0.dump
deleted file mode 100644
index 4335275..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=39868]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[Picture: mimeType=image/png, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.1.dump
deleted file mode 100644
index 0ae0f8b..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=39868]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[Picture: mimeType=image/png, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.2.dump
deleted file mode 100644
index 9b9e867..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=39868]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[Picture: mimeType=image/png, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.3.dump
deleted file mode 100644
index d5cb967..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=39868]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[Picture: mimeType=image/png, description=]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.0.dump b/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.0.dump
deleted file mode 100644
index 24ab87c..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.1.dump b/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.1.dump
deleted file mode 100644
index ec39040..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.2.dump b/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.2.dump
deleted file mode 100644
index 9453f11..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.3.dump b/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.3.dump
deleted file mode 100644
index 8d8fd28..0000000
--- a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac.3.dump
+++ /dev/null
@@ -1,40 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8880]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 3829
- sample count = 2
- sample 0:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 1:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flv/sample.flv.0.dump b/tree/library/extractor/src/test/assets/flv/sample.flv.0.dump
deleted file mode 100644
index 753896a..0000000
--- a/tree/library/extractor/src/test/assets/flv/sample.flv.0.dump
+++ /dev/null
@@ -1,359 +0,0 @@
-seekMap:
- isSeekable = false
- duration = 1136000
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 8:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 9529
- sample count = 45
- sample 0:
- time = 112000
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 135000
- flags = 1
- data = length 6, hash 31EC5206
- sample 2:
- time = 158000
- flags = 1
- data = length 148, hash 894A176B
- sample 3:
- time = 181000
- flags = 1
- data = length 189, hash CEF235A1
- sample 4:
- time = 205000
- flags = 1
- data = length 205, hash BBF5F7B0
- sample 5:
- time = 228000
- flags = 1
- data = length 210, hash F278B193
- sample 6:
- time = 251000
- flags = 1
- data = length 210, hash 82DA1589
- sample 7:
- time = 274000
- flags = 1
- data = length 207, hash 5BE231DF
- sample 8:
- time = 298000
- flags = 1
- data = length 225, hash 18819EE1
- sample 9:
- time = 321000
- flags = 1
- data = length 215, hash CA7FA67B
- sample 10:
- time = 344000
- flags = 1
- data = length 211, hash 581A1C18
- sample 11:
- time = 367000
- flags = 1
- data = length 216, hash ADB88187
- sample 12:
- time = 390000
- flags = 1
- data = length 229, hash 2E8BA4DC
- sample 13:
- time = 414000
- flags = 1
- data = length 232, hash 22F0C510
- sample 14:
- time = 437000
- flags = 1
- data = length 235, hash 867AD0DC
- sample 15:
- time = 460000
- flags = 1
- data = length 231, hash 84E823A8
- sample 16:
- time = 483000
- flags = 1
- data = length 226, hash 1BEF3A95
- sample 17:
- time = 507000
- flags = 1
- data = length 216, hash EAA345AE
- sample 18:
- time = 530000
- flags = 1
- data = length 229, hash 6957411F
- sample 19:
- time = 553000
- flags = 1
- data = length 219, hash 41275022
- sample 20:
- time = 576000
- flags = 1
- data = length 241, hash 6495DF96
- sample 21:
- time = 599000
- flags = 1
- data = length 228, hash 63D95906
- sample 22:
- time = 623000
- flags = 1
- data = length 238, hash 34F676F9
- sample 23:
- time = 646000
- flags = 1
- data = length 234, hash E5CBC045
- sample 24:
- time = 669000
- flags = 1
- data = length 231, hash 5FC43661
- sample 25:
- time = 692000
- flags = 1
- data = length 217, hash 682708ED
- sample 26:
- time = 716000
- flags = 1
- data = length 239, hash D43780FC
- sample 27:
- time = 739000
- flags = 1
- data = length 243, hash C5E17980
- sample 28:
- time = 762000
- flags = 1
- data = length 231, hash AC5837BA
- sample 29:
- time = 785000
- flags = 1
- data = length 230, hash 169EE895
- sample 30:
- time = 808000
- flags = 1
- data = length 238, hash C48FF3F1
- sample 31:
- time = 832000
- flags = 1
- data = length 225, hash 531E4599
- sample 32:
- time = 855000
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 33:
- time = 878000
- flags = 1
- data = length 243, hash F8C94C7
- sample 34:
- time = 901000
- flags = 1
- data = length 232, hash A646A7D0
- sample 35:
- time = 925000
- flags = 1
- data = length 237, hash E8B787A5
- sample 36:
- time = 948000
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 37:
- time = 971000
- flags = 1
- data = length 235, hash B9B33B0A
- sample 38:
- time = 994000
- flags = 1
- data = length 264, hash 71A4869E
- sample 39:
- time = 1017000
- flags = 1
- data = length 257, hash D049B54C
- sample 40:
- time = 1041000
- flags = 1
- data = length 227, hash 66757231
- sample 41:
- time = 1064000
- flags = 1
- data = length 227, hash BD374F1B
- sample 42:
- time = 1087000
- flags = 1
- data = length 235, hash 999477F6
- sample 43:
- time = 1110000
- flags = 1
- data = length 229, hash FFF98DF0
- sample 44:
- time = 1134000
- flags = 1
- data = length 6, hash 31B22286
-track 9:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash F6F3D010
- data = length 10, hash 7A0D0F2B
- total output bytes = 89502
- sample count = 30
- sample 0:
- time = 67000
- flags = 1
- data = length 36477, hash F0F36CFE
- sample 1:
- time = 134000
- flags = 0
- data = length 5341, hash 40B85E2
- sample 2:
- time = 100000
- flags = 0
- data = length 596, hash 357B4D92
- sample 3:
- time = 267000
- flags = 0
- data = length 7704, hash A39EDA06
- sample 4:
- time = 200000
- flags = 0
- data = length 989, hash 2813C72D
- sample 5:
- time = 167000
- flags = 0
- data = length 721, hash C50D1C73
- sample 6:
- time = 234000
- flags = 0
- data = length 519, hash 65FE1911
- sample 7:
- time = 400000
- flags = 0
- data = length 6160, hash E1CAC0EC
- sample 8:
- time = 334000
- flags = 0
- data = length 953, hash 7160C661
- sample 9:
- time = 300000
- flags = 0
- data = length 620, hash 7A7AE07C
- sample 10:
- time = 367000
- flags = 0
- data = length 405, hash 5CC7F4E7
- sample 11:
- time = 500000
- flags = 0
- data = length 4852, hash 9DB6979D
- sample 12:
- time = 467000
- flags = 0
- data = length 547, hash E31A6979
- sample 13:
- time = 434000
- flags = 0
- data = length 570, hash FEC40D00
- sample 14:
- time = 634000
- flags = 0
- data = length 5525, hash 7C478F7E
- sample 15:
- time = 567000
- flags = 0
- data = length 1082, hash DA07059A
- sample 16:
- time = 534000
- flags = 0
- data = length 807, hash 93478E6B
- sample 17:
- time = 600000
- flags = 0
- data = length 744, hash 9A8E6026
- sample 18:
- time = 767000
- flags = 0
- data = length 4732, hash C73B23C0
- sample 19:
- time = 700000
- flags = 0
- data = length 1004, hash 8A19A228
- sample 20:
- time = 667000
- flags = 0
- data = length 794, hash 8126022C
- sample 21:
- time = 734000
- flags = 0
- data = length 645, hash F08300E5
- sample 22:
- time = 900000
- flags = 0
- data = length 2684, hash 727FE378
- sample 23:
- time = 834000
- flags = 0
- data = length 787, hash 419A7821
- sample 24:
- time = 800000
- flags = 0
- data = length 649, hash 5C159346
- sample 25:
- time = 867000
- flags = 0
- data = length 509, hash F912D655
- sample 26:
- time = 1034000
- flags = 0
- data = length 1226, hash 29815C21
- sample 27:
- time = 967000
- flags = 0
- data = length 898, hash D997AD0A
- sample 28:
- time = 934000
- flags = 0
- data = length 476, hash A0423645
- sample 29:
- time = 1000000
- flags = 0
- data = length 486, hash DDF32CBB
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.0.dump b/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.0.dump
deleted file mode 100644
index 70b22f1..0000000
--- a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.0.dump
+++ /dev/null
@@ -1,43 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 8901000
- getPosition(0) = [[timeUs=0, position=5401]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/x-subrip
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 213
- sample count = 3
- sample 0:
- time = 0
- flags = 1
- data = length 59, hash 1AD38625
- sample 1:
- time = 2345000
- flags = 1
- data = length 95, hash F331C282
- sample 2:
- time = 4567000
- flags = 1
- data = length 59, hash F8CD7C60
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.1.dump b/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.1.dump
deleted file mode 100644
index 70b22f1..0000000
--- a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.1.dump
+++ /dev/null
@@ -1,43 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 8901000
- getPosition(0) = [[timeUs=0, position=5401]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/x-subrip
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 213
- sample count = 3
- sample 0:
- time = 0
- flags = 1
- data = length 59, hash 1AD38625
- sample 1:
- time = 2345000
- flags = 1
- data = length 95, hash F331C282
- sample 2:
- time = 4567000
- flags = 1
- data = length 59, hash F8CD7C60
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.2.dump b/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.2.dump
deleted file mode 100644
index 70b22f1..0000000
--- a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.2.dump
+++ /dev/null
@@ -1,43 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 8901000
- getPosition(0) = [[timeUs=0, position=5401]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/x-subrip
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 213
- sample count = 3
- sample 0:
- time = 0
- flags = 1
- data = length 59, hash 1AD38625
- sample 1:
- time = 2345000
- flags = 1
- data = length 95, hash F331C282
- sample 2:
- time = 4567000
- flags = 1
- data = length 59, hash F8CD7C60
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.3.dump b/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.3.dump
deleted file mode 100644
index 70b22f1..0000000
--- a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv.3.dump
+++ /dev/null
@@ -1,43 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 8901000
- getPosition(0) = [[timeUs=0, position=5401]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/x-subrip
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 213
- sample count = 3
- sample 0:
- time = 0
- flags = 1
- data = length 59, hash 1AD38625
- sample 1:
- time = 2345000
- flags = 1
- data = length 95, hash F331C282
- sample 2:
- time = 4567000
- flags = 1
- data = length 59, hash F8CD7C60
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/sample.mkv.0.dump b/tree/library/extractor/src/test/assets/mkv/sample.mkv.0.dump
deleted file mode 100644
index d53aa8e..0000000
--- a/tree/library/extractor/src/test/assets/mkv/sample.mkv.0.dump
+++ /dev/null
@@ -1,294 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1104000
- getPosition(0) = [[timeUs=67000, position=5576]]
-numberOfTracks = 2
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash F6F3D010
- data = length 10, hash 7A0D0F2B
- total output bytes = 89502
- sample count = 30
- sample 0:
- time = 67000
- flags = 1
- data = length 36477, hash F0F36CFE
- sample 1:
- time = 134000
- flags = 0
- data = length 5341, hash 40B85E2
- sample 2:
- time = 100000
- flags = 0
- data = length 596, hash 357B4D92
- sample 3:
- time = 267000
- flags = 0
- data = length 7704, hash A39EDA06
- sample 4:
- time = 200000
- flags = 0
- data = length 989, hash 2813C72D
- sample 5:
- time = 167000
- flags = 0
- data = length 721, hash C50D1C73
- sample 6:
- time = 234000
- flags = 0
- data = length 519, hash 65FE1911
- sample 7:
- time = 400000
- flags = 0
- data = length 6160, hash E1CAC0EC
- sample 8:
- time = 334000
- flags = 0
- data = length 953, hash 7160C661
- sample 9:
- time = 300000
- flags = 0
- data = length 620, hash 7A7AE07C
- sample 10:
- time = 367000
- flags = 0
- data = length 405, hash 5CC7F4E7
- sample 11:
- time = 500000
- flags = 0
- data = length 4852, hash 9DB6979D
- sample 12:
- time = 467000
- flags = 0
- data = length 547, hash E31A6979
- sample 13:
- time = 434000
- flags = 0
- data = length 570, hash FEC40D00
- sample 14:
- time = 634000
- flags = 0
- data = length 5525, hash 7C478F7E
- sample 15:
- time = 567000
- flags = 0
- data = length 1082, hash DA07059A
- sample 16:
- time = 534000
- flags = 0
- data = length 807, hash 93478E6B
- sample 17:
- time = 600000
- flags = 0
- data = length 744, hash 9A8E6026
- sample 18:
- time = 767000
- flags = 0
- data = length 4732, hash C73B23C0
- sample 19:
- time = 700000
- flags = 0
- data = length 1004, hash 8A19A228
- sample 20:
- time = 667000
- flags = 0
- data = length 794, hash 8126022C
- sample 21:
- time = 734000
- flags = 0
- data = length 645, hash F08300E5
- sample 22:
- time = 900000
- flags = 0
- data = length 2684, hash 727FE378
- sample 23:
- time = 834000
- flags = 0
- data = length 787, hash 419A7821
- sample 24:
- time = 800000
- flags = 0
- data = length 649, hash 5C159346
- sample 25:
- time = 867000
- flags = 0
- data = length 509, hash F912D655
- sample 26:
- time = 1034000
- flags = 0
- data = length 1226, hash 29815C21
- sample 27:
- time = 967000
- flags = 0
- data = length 898, hash D997AD0A
- sample 28:
- time = 934000
- flags = 0
- data = length 476, hash A0423645
- sample 29:
- time = 1000000
- flags = 0
- data = length 486, hash DDF32CBB
-track 2:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/ac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 12120
- sample count = 29
- sample 0:
- time = 129000
- flags = 1
- data = length 416, hash 211F2286
- sample 1:
- time = 164000
- flags = 1
- data = length 418, hash 77425A86
- sample 2:
- time = 198829
- flags = 1
- data = length 418, hash A0FE5CA1
- sample 3:
- time = 233000
- flags = 1
- data = length 418, hash 2309B066
- sample 4:
- time = 268000
- flags = 1
- data = length 418, hash 928A653B
- sample 5:
- time = 303000
- flags = 1
- data = length 418, hash 3422F0CB
- sample 6:
- time = 337829
- flags = 1
- data = length 418, hash EFF43D5B
- sample 7:
- time = 373000
- flags = 1
- data = length 418, hash FC8093C7
- sample 8:
- time = 408000
- flags = 1
- data = length 418, hash CCC08A16
- sample 9:
- time = 443000
- flags = 1
- data = length 418, hash 2A6EE863
- sample 10:
- time = 477829
- flags = 1
- data = length 418, hash D69A9251
- sample 11:
- time = 512000
- flags = 1
- data = length 418, hash BCFB758D
- sample 12:
- time = 547000
- flags = 1
- data = length 418, hash 11B66799
- sample 13:
- time = 581829
- flags = 1
- data = length 418, hash C824D392
- sample 14:
- time = 617000
- flags = 1
- data = length 418, hash C167D872
- sample 15:
- time = 652000
- flags = 1
- data = length 418, hash 4221C855
- sample 16:
- time = 687000
- flags = 1
- data = length 418, hash 4D4FF934
- sample 17:
- time = 721829
- flags = 1
- data = length 418, hash 984AA025
- sample 18:
- time = 757000
- flags = 1
- data = length 418, hash BB788B46
- sample 19:
- time = 791000
- flags = 1
- data = length 418, hash 9EFBFD97
- sample 20:
- time = 826000
- flags = 1
- data = length 418, hash DF1A460C
- sample 21:
- time = 860829
- flags = 1
- data = length 418, hash 2BDB56A
- sample 22:
- time = 896000
- flags = 1
- data = length 418, hash CA230060
- sample 23:
- time = 931000
- flags = 1
- data = length 418, hash D2F19F41
- sample 24:
- time = 965000
- flags = 1
- data = length 418, hash AF392D79
- sample 25:
- time = 999829
- flags = 1
- data = length 418, hash C5D7F2A3
- sample 26:
- time = 1035000
- flags = 1
- data = length 418, hash 733A35AE
- sample 27:
- time = 1069829
- flags = 1
- data = length 418, hash DE46E5D3
- sample 28:
- time = 1104000
- flags = 1
- data = length 418, hash 56AB8D37
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/sample.mkv.1.dump b/tree/library/extractor/src/test/assets/mkv/sample.mkv.1.dump
deleted file mode 100644
index 3bc74fa..0000000
--- a/tree/library/extractor/src/test/assets/mkv/sample.mkv.1.dump
+++ /dev/null
@@ -1,222 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1104000
- getPosition(0) = [[timeUs=67000, position=5576]]
-numberOfTracks = 2
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash F6F3D010
- data = length 10, hash 7A0D0F2B
- total output bytes = 29422
- sample count = 20
- sample 0:
- time = 367000
- flags = 0
- data = length 405, hash 5CC7F4E7
- sample 1:
- time = 500000
- flags = 0
- data = length 4852, hash 9DB6979D
- sample 2:
- time = 467000
- flags = 0
- data = length 547, hash E31A6979
- sample 3:
- time = 434000
- flags = 0
- data = length 570, hash FEC40D00
- sample 4:
- time = 634000
- flags = 0
- data = length 5525, hash 7C478F7E
- sample 5:
- time = 567000
- flags = 0
- data = length 1082, hash DA07059A
- sample 6:
- time = 534000
- flags = 0
- data = length 807, hash 93478E6B
- sample 7:
- time = 600000
- flags = 0
- data = length 744, hash 9A8E6026
- sample 8:
- time = 767000
- flags = 0
- data = length 4732, hash C73B23C0
- sample 9:
- time = 700000
- flags = 0
- data = length 1004, hash 8A19A228
- sample 10:
- time = 667000
- flags = 0
- data = length 794, hash 8126022C
- sample 11:
- time = 734000
- flags = 0
- data = length 645, hash F08300E5
- sample 12:
- time = 900000
- flags = 0
- data = length 2684, hash 727FE378
- sample 13:
- time = 834000
- flags = 0
- data = length 787, hash 419A7821
- sample 14:
- time = 800000
- flags = 0
- data = length 649, hash 5C159346
- sample 15:
- time = 867000
- flags = 0
- data = length 509, hash F912D655
- sample 16:
- time = 1034000
- flags = 0
- data = length 1226, hash 29815C21
- sample 17:
- time = 967000
- flags = 0
- data = length 898, hash D997AD0A
- sample 18:
- time = 934000
- flags = 0
- data = length 476, hash A0423645
- sample 19:
- time = 1000000
- flags = 0
- data = length 486, hash DDF32CBB
-track 2:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/ac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 8778
- sample count = 21
- sample 0:
- time = 408000
- flags = 1
- data = length 418, hash CCC08A16
- sample 1:
- time = 443000
- flags = 1
- data = length 418, hash 2A6EE863
- sample 2:
- time = 477829
- flags = 1
- data = length 418, hash D69A9251
- sample 3:
- time = 512000
- flags = 1
- data = length 418, hash BCFB758D
- sample 4:
- time = 547000
- flags = 1
- data = length 418, hash 11B66799
- sample 5:
- time = 581829
- flags = 1
- data = length 418, hash C824D392
- sample 6:
- time = 617000
- flags = 1
- data = length 418, hash C167D872
- sample 7:
- time = 652000
- flags = 1
- data = length 418, hash 4221C855
- sample 8:
- time = 687000
- flags = 1
- data = length 418, hash 4D4FF934
- sample 9:
- time = 721829
- flags = 1
- data = length 418, hash 984AA025
- sample 10:
- time = 757000
- flags = 1
- data = length 418, hash BB788B46
- sample 11:
- time = 791000
- flags = 1
- data = length 418, hash 9EFBFD97
- sample 12:
- time = 826000
- flags = 1
- data = length 418, hash DF1A460C
- sample 13:
- time = 860829
- flags = 1
- data = length 418, hash 2BDB56A
- sample 14:
- time = 896000
- flags = 1
- data = length 418, hash CA230060
- sample 15:
- time = 931000
- flags = 1
- data = length 418, hash D2F19F41
- sample 16:
- time = 965000
- flags = 1
- data = length 418, hash AF392D79
- sample 17:
- time = 999829
- flags = 1
- data = length 418, hash C5D7F2A3
- sample 18:
- time = 1035000
- flags = 1
- data = length 418, hash 733A35AE
- sample 19:
- time = 1069829
- flags = 1
- data = length 418, hash DE46E5D3
- sample 20:
- time = 1104000
- flags = 1
- data = length 418, hash 56AB8D37
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/sample.mkv.2.dump b/tree/library/extractor/src/test/assets/mkv/sample.mkv.2.dump
deleted file mode 100644
index 1978df9..0000000
--- a/tree/library/extractor/src/test/assets/mkv/sample.mkv.2.dump
+++ /dev/null
@@ -1,134 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1104000
- getPosition(0) = [[timeUs=67000, position=5576]]
-numberOfTracks = 2
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash F6F3D010
- data = length 10, hash 7A0D0F2B
- total output bytes = 8360
- sample count = 9
- sample 0:
- time = 734000
- flags = 0
- data = length 645, hash F08300E5
- sample 1:
- time = 900000
- flags = 0
- data = length 2684, hash 727FE378
- sample 2:
- time = 834000
- flags = 0
- data = length 787, hash 419A7821
- sample 3:
- time = 800000
- flags = 0
- data = length 649, hash 5C159346
- sample 4:
- time = 867000
- flags = 0
- data = length 509, hash F912D655
- sample 5:
- time = 1034000
- flags = 0
- data = length 1226, hash 29815C21
- sample 6:
- time = 967000
- flags = 0
- data = length 898, hash D997AD0A
- sample 7:
- time = 934000
- flags = 0
- data = length 476, hash A0423645
- sample 8:
- time = 1000000
- flags = 0
- data = length 486, hash DDF32CBB
-track 2:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/ac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 4180
- sample count = 10
- sample 0:
- time = 791000
- flags = 1
- data = length 418, hash 9EFBFD97
- sample 1:
- time = 826000
- flags = 1
- data = length 418, hash DF1A460C
- sample 2:
- time = 860829
- flags = 1
- data = length 418, hash 2BDB56A
- sample 3:
- time = 896000
- flags = 1
- data = length 418, hash CA230060
- sample 4:
- time = 931000
- flags = 1
- data = length 418, hash D2F19F41
- sample 5:
- time = 965000
- flags = 1
- data = length 418, hash AF392D79
- sample 6:
- time = 999829
- flags = 1
- data = length 418, hash C5D7F2A3
- sample 7:
- time = 1035000
- flags = 1
- data = length 418, hash 733A35AE
- sample 8:
- time = 1069829
- flags = 1
- data = length 418, hash DE46E5D3
- sample 9:
- time = 1104000
- flags = 1
- data = length 418, hash 56AB8D37
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/sample.mkv.3.dump b/tree/library/extractor/src/test/assets/mkv/sample.mkv.3.dump
deleted file mode 100644
index fb88891..0000000
--- a/tree/library/extractor/src/test/assets/mkv/sample.mkv.3.dump
+++ /dev/null
@@ -1,70 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1104000
- getPosition(0) = [[timeUs=67000, position=5576]]
-numberOfTracks = 2
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash F6F3D010
- data = length 10, hash 7A0D0F2B
- total output bytes = 0
- sample count = 0
-track 2:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/ac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 1
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 1254
- sample count = 3
- sample 0:
- time = 1035000
- flags = 1
- data = length 418, hash 733A35AE
- sample 1:
- time = 1069829
- flags = 1
- data = length 418, hash DE46E5D3
- sample 2:
- time = 1104000
- flags = 1
- data = length 418, hash 56AB8D37
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump b/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump
deleted file mode 100644
index e20c21d..0000000
--- a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump
+++ /dev/null
@@ -1,37 +0,0 @@
-seekMap:
- isSeekable = false
- duration = 1000
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/x-vnd.on2.vp9
- maxInputSize = -1
- width = 360
- height = 240
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = 1305012705
- metadata = null
- initializationData:
- total output bytes = 39
- sample count = 1
- sample 0:
- time = 0
- flags = 1073741824
- data = length 39, hash B7FE77F4
- crypto mode = 1
- encryption key = length 16, hash 4CE944CF
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump b/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump
deleted file mode 100644
index 904ca87..0000000
--- a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump
+++ /dev/null
@@ -1,37 +0,0 @@
-seekMap:
- isSeekable = false
- duration = 1000
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/x-vnd.on2.vp9
- maxInputSize = -1
- width = 360
- height = 240
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = 1305012705
- metadata = null
- initializationData:
- total output bytes = 24
- sample count = 1
- sample 0:
- time = 0
- flags = 1073741824
- data = length 24, hash E58668B1
- crypto mode = 1
- encryption key = length 16, hash 4CE944CF
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/bear.mp3 b/tree/library/extractor/src/test/assets/mp3/bear.mp3
deleted file mode 100644
index 0c1001c..0000000
--- a/tree/library/extractor/src/test/assets/mp3/bear.mp3
+++ /dev/null
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/mp3/bear.mp3.0.dump b/tree/library/extractor/src/test/assets/mp3/bear.mp3.0.dump
deleted file mode 100644
index e597f0a..0000000
--- a/tree/library/extractor/src/test/assets/mp3/bear.mp3.0.dump
+++ /dev/null
@@ -1,495 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2784000
- getPosition(0) = [[timeUs=0, position=201]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 956
- encoderPadding = 3352
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf54.20.4]
- initializationData:
- total output bytes = 44544
- sample count = 116
- sample 0:
- time = 0
- flags = 1
- data = length 384, hash B1FBF8BD
- sample 1:
- time = 24000
- flags = 1
- data = length 384, hash 2B9A3B72
- sample 2:
- time = 48000
- flags = 1
- data = length 384, hash 33C65BA6
- sample 3:
- time = 72000
- flags = 1
- data = length 384, hash E64FE475
- sample 4:
- time = 96000
- flags = 1
- data = length 384, hash E9122D34
- sample 5:
- time = 120000
- flags = 1
- data = length 384, hash 9CC87327
- sample 6:
- time = 144000
- flags = 1
- data = length 384, hash 118CF6DA
- sample 7:
- time = 168000
- flags = 1
- data = length 384, hash 9610D9D6
- sample 8:
- time = 192000
- flags = 1
- data = length 384, hash 6ABFE405
- sample 9:
- time = 216000
- flags = 1
- data = length 384, hash EE5C93A9
- sample 10:
- time = 240000
- flags = 1
- data = length 384, hash 44E0D140
- sample 11:
- time = 264000
- flags = 1
- data = length 384, hash 3B3DE1D6
- sample 12:
- time = 288000
- flags = 1
- data = length 384, hash 3A572E7C
- sample 13:
- time = 312000
- flags = 1
- data = length 384, hash 240316E1
- sample 14:
- time = 336000
- flags = 1
- data = length 384, hash 9EDA9AA0
- sample 15:
- time = 360000
- flags = 1
- data = length 384, hash E31AB44F
- sample 16:
- time = 384000
- flags = 1
- data = length 384, hash A12497D6
- sample 17:
- time = 408000
- flags = 1
- data = length 384, hash 8A179B75
- sample 18:
- time = 432000
- flags = 1
- data = length 384, hash FCE9E107
- sample 19:
- time = 456000
- flags = 1
- data = length 384, hash 52CA9665
- sample 20:
- time = 480000
- flags = 1
- data = length 384, hash 9935EC4C
- sample 21:
- time = 504000
- flags = 1
- data = length 384, hash 33CA710A
- sample 22:
- time = 528000
- flags = 1
- data = length 384, hash 45B5D69
- sample 23:
- time = 552000
- flags = 1
- data = length 384, hash 7CEC655D
- sample 24:
- time = 576000
- flags = 1
- data = length 384, hash 3B5D8310
- sample 25:
- time = 600000
- flags = 1
- data = length 384, hash 3EB640F8
- sample 26:
- time = 624000
- flags = 1
- data = length 384, hash FAEC53B4
- sample 27:
- time = 648000
- flags = 1
- data = length 384, hash 92C8A6EE
- sample 28:
- time = 672000
- flags = 1
- data = length 384, hash 7CBAAE91
- sample 29:
- time = 696000
- flags = 1
- data = length 384, hash 74AC754E
- sample 30:
- time = 720000
- flags = 1
- data = length 384, hash 8242C434
- sample 31:
- time = 744000
- flags = 1
- data = length 384, hash 686C06FB
- sample 32:
- time = 768000
- flags = 1
- data = length 384, hash 1D872A3F
- sample 33:
- time = 792000
- flags = 1
- data = length 384, hash 900A20BC
- sample 34:
- time = 816000
- flags = 1
- data = length 384, hash B72FD8E7
- sample 35:
- time = 840000
- flags = 1
- data = length 384, hash 85C9A1FB
- sample 36:
- time = 864000
- flags = 1
- data = length 384, hash 1600DF3
- sample 37:
- time = 888000
- flags = 1
- data = length 384, hash D6C2138A
- sample 38:
- time = 912000
- flags = 1
- data = length 384, hash 737BA69E
- sample 39:
- time = 936000
- flags = 1
- data = length 384, hash F7E344F4
- sample 40:
- time = 960000
- flags = 1
- data = length 384, hash 14EF6AFD
- sample 41:
- time = 984000
- flags = 1
- data = length 384, hash 61C9B92C
- sample 42:
- time = 1008000
- flags = 1
- data = length 384, hash ABE1368
- sample 43:
- time = 1032000
- flags = 1
- data = length 384, hash 6A3B8547
- sample 44:
- time = 1056000
- flags = 1
- data = length 384, hash 30E905FA
- sample 45:
- time = 1080000
- flags = 1
- data = length 384, hash 21A267CD
- sample 46:
- time = 1104000
- flags = 1
- data = length 384, hash D96A2651
- sample 47:
- time = 1128000
- flags = 1
- data = length 384, hash 72340177
- sample 48:
- time = 1152000
- flags = 1
- data = length 384, hash 9345E744
- sample 49:
- time = 1176000
- flags = 1
- data = length 384, hash FDE39E3A
- sample 50:
- time = 1200000
- flags = 1
- data = length 384, hash F0B7465
- sample 51:
- time = 1224000
- flags = 1
- data = length 384, hash 3693AB86
- sample 52:
- time = 1248000
- flags = 1
- data = length 384, hash F39719B1
- sample 53:
- time = 1272000
- flags = 1
- data = length 384, hash DA3958DC
- sample 54:
- time = 1296000
- flags = 1
- data = length 384, hash FDC7599F
- sample 55:
- time = 1320000
- flags = 1
- data = length 384, hash AEFF8471
- sample 56:
- time = 1344000
- flags = 1
- data = length 384, hash 89C92C19
- sample 57:
- time = 1368000
- flags = 1
- data = length 384, hash 5C786A4B
- sample 58:
- time = 1392000
- flags = 1
- data = length 384, hash 5ACA8B
- sample 59:
- time = 1416000
- flags = 1
- data = length 384, hash 7755974C
- sample 60:
- time = 1440000
- flags = 1
- data = length 384, hash 3934B73C
- sample 61:
- time = 1464000
- flags = 1
- data = length 384, hash DDD70A2F
- sample 62:
- time = 1488000
- flags = 1
- data = length 384, hash 8FACE2EF
- sample 63:
- time = 1512000
- flags = 1
- data = length 384, hash 4A602591
- sample 64:
- time = 1536000
- flags = 1
- data = length 384, hash D019AA2D
- sample 65:
- time = 1560000
- flags = 1
- data = length 384, hash 8A680B9D
- sample 66:
- time = 1584000
- flags = 1
- data = length 384, hash B655C959
- sample 67:
- time = 1608000
- flags = 1
- data = length 384, hash 2168336B
- sample 68:
- time = 1632000
- flags = 1
- data = length 384, hash D77F6D31
- sample 69:
- time = 1656000
- flags = 1
- data = length 384, hash 524B4B2F
- sample 70:
- time = 1680000
- flags = 1
- data = length 384, hash 4752DDFC
- sample 71:
- time = 1704000
- flags = 1
- data = length 384, hash E786727F
- sample 72:
- time = 1728000
- flags = 1
- data = length 384, hash 5DA6FB8C
- sample 73:
- time = 1752000
- flags = 1
- data = length 384, hash 92F24269
- sample 74:
- time = 1776000
- flags = 1
- data = length 384, hash CD0A3BA1
- sample 75:
- time = 1800000
- flags = 1
- data = length 384, hash 7D00409F
- sample 76:
- time = 1824000
- flags = 1
- data = length 384, hash D7ADB5FA
- sample 77:
- time = 1848000
- flags = 1
- data = length 384, hash 4A140209
- sample 78:
- time = 1872000
- flags = 1
- data = length 384, hash E801184A
- sample 79:
- time = 1896000
- flags = 1
- data = length 384, hash 53C6CF9C
- sample 80:
- time = 1920000
- flags = 1
- data = length 384, hash 19A8D99F
- sample 81:
- time = 1944000
- flags = 1
- data = length 384, hash E47EB43F
- sample 82:
- time = 1968000
- flags = 1
- data = length 384, hash 4EA329E7
- sample 83:
- time = 1992000
- flags = 1
- data = length 384, hash 1CCAAE62
- sample 84:
- time = 2016000
- flags = 1
- data = length 384, hash ED3F8C66
- sample 85:
- time = 2040000
- flags = 1
- data = length 384, hash D3D646B6
- sample 86:
- time = 2064000
- flags = 1
- data = length 384, hash 68CD1574
- sample 87:
- time = 2088000
- flags = 1
- data = length 384, hash 8CEAB382
- sample 88:
- time = 2112000
- flags = 1
- data = length 384, hash D54B1C48
- sample 89:
- time = 2136000
- flags = 1
- data = length 384, hash FFE2EE90
- sample 90:
- time = 2160000
- flags = 1
- data = length 384, hash BFE8A673
- sample 91:
- time = 2184000
- flags = 1
- data = length 384, hash 978B1C92
- sample 92:
- time = 2208000
- flags = 1
- data = length 384, hash 810CC71E
- sample 93:
- time = 2232000
- flags = 1
- data = length 384, hash 44FE42D9
- sample 94:
- time = 2256000
- flags = 1
- data = length 384, hash 2F5BB02C
- sample 95:
- time = 2280000
- flags = 1
- data = length 384, hash 77DDB90
- sample 96:
- time = 2304000
- flags = 1
- data = length 384, hash 24FB5EDA
- sample 97:
- time = 2328000
- flags = 1
- data = length 384, hash E73203C6
- sample 98:
- time = 2352000
- flags = 1
- data = length 384, hash 14B525F1
- sample 99:
- time = 2376000
- flags = 1
- data = length 384, hash 5E0F4E2E
- sample 100:
- time = 2400000
- flags = 1
- data = length 384, hash 67EE4E31
- sample 101:
- time = 2424000
- flags = 1
- data = length 384, hash 2E04EC4C
- sample 102:
- time = 2448000
- flags = 1
- data = length 384, hash 852CABA7
- sample 103:
- time = 2472000
- flags = 1
- data = length 384, hash 19928903
- sample 104:
- time = 2496000
- flags = 1
- data = length 384, hash 5DA42021
- sample 105:
- time = 2520000
- flags = 1
- data = length 384, hash 45B20B7C
- sample 106:
- time = 2544000
- flags = 1
- data = length 384, hash D108A215
- sample 107:
- time = 2568000
- flags = 1
- data = length 384, hash BD25DB7C
- sample 108:
- time = 2592000
- flags = 1
- data = length 384, hash DA7F9861
- sample 109:
- time = 2616000
- flags = 1
- data = length 384, hash CCD576F
- sample 110:
- time = 2640000
- flags = 1
- data = length 384, hash 405C1EB5
- sample 111:
- time = 2664000
- flags = 1
- data = length 384, hash 6640B74E
- sample 112:
- time = 2688000
- flags = 1
- data = length 384, hash B4E5937A
- sample 113:
- time = 2712000
- flags = 1
- data = length 384, hash CEE17733
- sample 114:
- time = 2736000
- flags = 1
- data = length 384, hash 2A0DA733
- sample 115:
- time = 2760000
- flags = 1
- data = length 384, hash 97F4129B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/bear.mp3.1.dump b/tree/library/extractor/src/test/assets/mp3/bear.mp3.1.dump
deleted file mode 100644
index a7f7f69..0000000
--- a/tree/library/extractor/src/test/assets/mp3/bear.mp3.1.dump
+++ /dev/null
@@ -1,339 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2784000
- getPosition(0) = [[timeUs=0, position=201]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 956
- encoderPadding = 3352
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf54.20.4]
- initializationData:
- total output bytes = 29568
- sample count = 77
- sample 0:
- time = 928568
- flags = 1
- data = length 384, hash F7E344F4
- sample 1:
- time = 952568
- flags = 1
- data = length 384, hash 14EF6AFD
- sample 2:
- time = 976568
- flags = 1
- data = length 384, hash 61C9B92C
- sample 3:
- time = 1000568
- flags = 1
- data = length 384, hash ABE1368
- sample 4:
- time = 1024568
- flags = 1
- data = length 384, hash 6A3B8547
- sample 5:
- time = 1048568
- flags = 1
- data = length 384, hash 30E905FA
- sample 6:
- time = 1072568
- flags = 1
- data = length 384, hash 21A267CD
- sample 7:
- time = 1096568
- flags = 1
- data = length 384, hash D96A2651
- sample 8:
- time = 1120568
- flags = 1
- data = length 384, hash 72340177
- sample 9:
- time = 1144568
- flags = 1
- data = length 384, hash 9345E744
- sample 10:
- time = 1168568
- flags = 1
- data = length 384, hash FDE39E3A
- sample 11:
- time = 1192568
- flags = 1
- data = length 384, hash F0B7465
- sample 12:
- time = 1216568
- flags = 1
- data = length 384, hash 3693AB86
- sample 13:
- time = 1240568
- flags = 1
- data = length 384, hash F39719B1
- sample 14:
- time = 1264568
- flags = 1
- data = length 384, hash DA3958DC
- sample 15:
- time = 1288568
- flags = 1
- data = length 384, hash FDC7599F
- sample 16:
- time = 1312568
- flags = 1
- data = length 384, hash AEFF8471
- sample 17:
- time = 1336568
- flags = 1
- data = length 384, hash 89C92C19
- sample 18:
- time = 1360568
- flags = 1
- data = length 384, hash 5C786A4B
- sample 19:
- time = 1384568
- flags = 1
- data = length 384, hash 5ACA8B
- sample 20:
- time = 1408568
- flags = 1
- data = length 384, hash 7755974C
- sample 21:
- time = 1432568
- flags = 1
- data = length 384, hash 3934B73C
- sample 22:
- time = 1456568
- flags = 1
- data = length 384, hash DDD70A2F
- sample 23:
- time = 1480568
- flags = 1
- data = length 384, hash 8FACE2EF
- sample 24:
- time = 1504568
- flags = 1
- data = length 384, hash 4A602591
- sample 25:
- time = 1528568
- flags = 1
- data = length 384, hash D019AA2D
- sample 26:
- time = 1552568
- flags = 1
- data = length 384, hash 8A680B9D
- sample 27:
- time = 1576568
- flags = 1
- data = length 384, hash B655C959
- sample 28:
- time = 1600568
- flags = 1
- data = length 384, hash 2168336B
- sample 29:
- time = 1624568
- flags = 1
- data = length 384, hash D77F6D31
- sample 30:
- time = 1648568
- flags = 1
- data = length 384, hash 524B4B2F
- sample 31:
- time = 1672568
- flags = 1
- data = length 384, hash 4752DDFC
- sample 32:
- time = 1696568
- flags = 1
- data = length 384, hash E786727F
- sample 33:
- time = 1720568
- flags = 1
- data = length 384, hash 5DA6FB8C
- sample 34:
- time = 1744568
- flags = 1
- data = length 384, hash 92F24269
- sample 35:
- time = 1768568
- flags = 1
- data = length 384, hash CD0A3BA1
- sample 36:
- time = 1792568
- flags = 1
- data = length 384, hash 7D00409F
- sample 37:
- time = 1816568
- flags = 1
- data = length 384, hash D7ADB5FA
- sample 38:
- time = 1840568
- flags = 1
- data = length 384, hash 4A140209
- sample 39:
- time = 1864568
- flags = 1
- data = length 384, hash E801184A
- sample 40:
- time = 1888568
- flags = 1
- data = length 384, hash 53C6CF9C
- sample 41:
- time = 1912568
- flags = 1
- data = length 384, hash 19A8D99F
- sample 42:
- time = 1936568
- flags = 1
- data = length 384, hash E47EB43F
- sample 43:
- time = 1960568
- flags = 1
- data = length 384, hash 4EA329E7
- sample 44:
- time = 1984568
- flags = 1
- data = length 384, hash 1CCAAE62
- sample 45:
- time = 2008568
- flags = 1
- data = length 384, hash ED3F8C66
- sample 46:
- time = 2032568
- flags = 1
- data = length 384, hash D3D646B6
- sample 47:
- time = 2056568
- flags = 1
- data = length 384, hash 68CD1574
- sample 48:
- time = 2080568
- flags = 1
- data = length 384, hash 8CEAB382
- sample 49:
- time = 2104568
- flags = 1
- data = length 384, hash D54B1C48
- sample 50:
- time = 2128568
- flags = 1
- data = length 384, hash FFE2EE90
- sample 51:
- time = 2152568
- flags = 1
- data = length 384, hash BFE8A673
- sample 52:
- time = 2176568
- flags = 1
- data = length 384, hash 978B1C92
- sample 53:
- time = 2200568
- flags = 1
- data = length 384, hash 810CC71E
- sample 54:
- time = 2224568
- flags = 1
- data = length 384, hash 44FE42D9
- sample 55:
- time = 2248568
- flags = 1
- data = length 384, hash 2F5BB02C
- sample 56:
- time = 2272568
- flags = 1
- data = length 384, hash 77DDB90
- sample 57:
- time = 2296568
- flags = 1
- data = length 384, hash 24FB5EDA
- sample 58:
- time = 2320568
- flags = 1
- data = length 384, hash E73203C6
- sample 59:
- time = 2344568
- flags = 1
- data = length 384, hash 14B525F1
- sample 60:
- time = 2368568
- flags = 1
- data = length 384, hash 5E0F4E2E
- sample 61:
- time = 2392568
- flags = 1
- data = length 384, hash 67EE4E31
- sample 62:
- time = 2416568
- flags = 1
- data = length 384, hash 2E04EC4C
- sample 63:
- time = 2440568
- flags = 1
- data = length 384, hash 852CABA7
- sample 64:
- time = 2464568
- flags = 1
- data = length 384, hash 19928903
- sample 65:
- time = 2488568
- flags = 1
- data = length 384, hash 5DA42021
- sample 66:
- time = 2512568
- flags = 1
- data = length 384, hash 45B20B7C
- sample 67:
- time = 2536568
- flags = 1
- data = length 384, hash D108A215
- sample 68:
- time = 2560568
- flags = 1
- data = length 384, hash BD25DB7C
- sample 69:
- time = 2584568
- flags = 1
- data = length 384, hash DA7F9861
- sample 70:
- time = 2608568
- flags = 1
- data = length 384, hash CCD576F
- sample 71:
- time = 2632568
- flags = 1
- data = length 384, hash 405C1EB5
- sample 72:
- time = 2656568
- flags = 1
- data = length 384, hash 6640B74E
- sample 73:
- time = 2680568
- flags = 1
- data = length 384, hash B4E5937A
- sample 74:
- time = 2704568
- flags = 1
- data = length 384, hash CEE17733
- sample 75:
- time = 2728568
- flags = 1
- data = length 384, hash 2A0DA733
- sample 76:
- time = 2752568
- flags = 1
- data = length 384, hash 97F4129B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/bear.mp3.2.dump b/tree/library/extractor/src/test/assets/mp3/bear.mp3.2.dump
deleted file mode 100644
index 981a141..0000000
--- a/tree/library/extractor/src/test/assets/mp3/bear.mp3.2.dump
+++ /dev/null
@@ -1,183 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2784000
- getPosition(0) = [[timeUs=0, position=201]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 956
- encoderPadding = 3352
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf54.20.4]
- initializationData:
- total output bytes = 14592
- sample count = 38
- sample 0:
- time = 1871586
- flags = 1
- data = length 384, hash E801184A
- sample 1:
- time = 1895586
- flags = 1
- data = length 384, hash 53C6CF9C
- sample 2:
- time = 1919586
- flags = 1
- data = length 384, hash 19A8D99F
- sample 3:
- time = 1943586
- flags = 1
- data = length 384, hash E47EB43F
- sample 4:
- time = 1967586
- flags = 1
- data = length 384, hash 4EA329E7
- sample 5:
- time = 1991586
- flags = 1
- data = length 384, hash 1CCAAE62
- sample 6:
- time = 2015586
- flags = 1
- data = length 384, hash ED3F8C66
- sample 7:
- time = 2039586
- flags = 1
- data = length 384, hash D3D646B6
- sample 8:
- time = 2063586
- flags = 1
- data = length 384, hash 68CD1574
- sample 9:
- time = 2087586
- flags = 1
- data = length 384, hash 8CEAB382
- sample 10:
- time = 2111586
- flags = 1
- data = length 384, hash D54B1C48
- sample 11:
- time = 2135586
- flags = 1
- data = length 384, hash FFE2EE90
- sample 12:
- time = 2159586
- flags = 1
- data = length 384, hash BFE8A673
- sample 13:
- time = 2183586
- flags = 1
- data = length 384, hash 978B1C92
- sample 14:
- time = 2207586
- flags = 1
- data = length 384, hash 810CC71E
- sample 15:
- time = 2231586
- flags = 1
- data = length 384, hash 44FE42D9
- sample 16:
- time = 2255586
- flags = 1
- data = length 384, hash 2F5BB02C
- sample 17:
- time = 2279586
- flags = 1
- data = length 384, hash 77DDB90
- sample 18:
- time = 2303586
- flags = 1
- data = length 384, hash 24FB5EDA
- sample 19:
- time = 2327586
- flags = 1
- data = length 384, hash E73203C6
- sample 20:
- time = 2351586
- flags = 1
- data = length 384, hash 14B525F1
- sample 21:
- time = 2375586
- flags = 1
- data = length 384, hash 5E0F4E2E
- sample 22:
- time = 2399586
- flags = 1
- data = length 384, hash 67EE4E31
- sample 23:
- time = 2423586
- flags = 1
- data = length 384, hash 2E04EC4C
- sample 24:
- time = 2447586
- flags = 1
- data = length 384, hash 852CABA7
- sample 25:
- time = 2471586
- flags = 1
- data = length 384, hash 19928903
- sample 26:
- time = 2495586
- flags = 1
- data = length 384, hash 5DA42021
- sample 27:
- time = 2519586
- flags = 1
- data = length 384, hash 45B20B7C
- sample 28:
- time = 2543586
- flags = 1
- data = length 384, hash D108A215
- sample 29:
- time = 2567586
- flags = 1
- data = length 384, hash BD25DB7C
- sample 30:
- time = 2591586
- flags = 1
- data = length 384, hash DA7F9861
- sample 31:
- time = 2615586
- flags = 1
- data = length 384, hash CCD576F
- sample 32:
- time = 2639586
- flags = 1
- data = length 384, hash 405C1EB5
- sample 33:
- time = 2663586
- flags = 1
- data = length 384, hash 6640B74E
- sample 34:
- time = 2687586
- flags = 1
- data = length 384, hash B4E5937A
- sample 35:
- time = 2711586
- flags = 1
- data = length 384, hash CEE17733
- sample 36:
- time = 2735586
- flags = 1
- data = length 384, hash 2A0DA733
- sample 37:
- time = 2759586
- flags = 1
- data = length 384, hash 97F4129B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/bear.mp3.3.dump b/tree/library/extractor/src/test/assets/mp3/bear.mp3.3.dump
deleted file mode 100644
index 744244b..0000000
--- a/tree/library/extractor/src/test/assets/mp3/bear.mp3.3.dump
+++ /dev/null
@@ -1,31 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2784000
- getPosition(0) = [[timeUs=0, position=201]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 956
- encoderPadding = 3352
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf54.20.4]
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.0.dump b/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.0.dump
deleted file mode 100644
index 75cf532..0000000
--- a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.0.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 26125
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 418
- sample count = 1
- sample 0:
- time = 0
- flags = 1
- data = length 418, hash B819987
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.1.dump b/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.1.dump
deleted file mode 100644
index 75cf532..0000000
--- a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.1.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 26125
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 418
- sample count = 1
- sample 0:
- time = 0
- flags = 1
- data = length 418, hash B819987
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.2.dump b/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.2.dump
deleted file mode 100644
index 75cf532..0000000
--- a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.2.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 26125
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 418
- sample count = 1
- sample 0:
- time = 0
- flags = 1
- data = length 418, hash B819987
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.3.dump b/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.3.dump
deleted file mode 100644
index 75cf532..0000000
--- a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 26125
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 418
- sample count = 1
- sample 0:
- time = 0
- flags = 1
- data = length 418, hash B819987
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.unklen.dump b/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.unklen.dump
deleted file mode 100644
index a7b9627..0000000
--- a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3.unklen.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/mpeg
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 418
- sample count = 1
- sample 0:
- time = 0
- flags = 1
- data = length 418, hash B819987
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample.mp4.0.dump
deleted file mode 100644
index 1b52465..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample.mp4.0.dump
+++ /dev/null
@@ -1,359 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=48]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 9529
- sample count = 45
- sample 0:
- time = 44000
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 67219
- flags = 1
- data = length 6, hash 31EC5206
- sample 2:
- time = 90439
- flags = 1
- data = length 148, hash 894A176B
- sample 3:
- time = 113659
- flags = 1
- data = length 189, hash CEF235A1
- sample 4:
- time = 136879
- flags = 1
- data = length 205, hash BBF5F7B0
- sample 5:
- time = 160099
- flags = 1
- data = length 210, hash F278B193
- sample 6:
- time = 183319
- flags = 1
- data = length 210, hash 82DA1589
- sample 7:
- time = 206539
- flags = 1
- data = length 207, hash 5BE231DF
- sample 8:
- time = 229759
- flags = 1
- data = length 225, hash 18819EE1
- sample 9:
- time = 252979
- flags = 1
- data = length 215, hash CA7FA67B
- sample 10:
- time = 276199
- flags = 1
- data = length 211, hash 581A1C18
- sample 11:
- time = 299419
- flags = 1
- data = length 216, hash ADB88187
- sample 12:
- time = 322639
- flags = 1
- data = length 229, hash 2E8BA4DC
- sample 13:
- time = 345859
- flags = 1
- data = length 232, hash 22F0C510
- sample 14:
- time = 369079
- flags = 1
- data = length 235, hash 867AD0DC
- sample 15:
- time = 392299
- flags = 1
- data = length 231, hash 84E823A8
- sample 16:
- time = 415519
- flags = 1
- data = length 226, hash 1BEF3A95
- sample 17:
- time = 438739
- flags = 1
- data = length 216, hash EAA345AE
- sample 18:
- time = 461959
- flags = 1
- data = length 229, hash 6957411F
- sample 19:
- time = 485179
- flags = 1
- data = length 219, hash 41275022
- sample 20:
- time = 508399
- flags = 1
- data = length 241, hash 6495DF96
- sample 21:
- time = 531619
- flags = 1
- data = length 228, hash 63D95906
- sample 22:
- time = 554839
- flags = 1
- data = length 238, hash 34F676F9
- sample 23:
- time = 578058
- flags = 1
- data = length 234, hash E5CBC045
- sample 24:
- time = 601278
- flags = 1
- data = length 231, hash 5FC43661
- sample 25:
- time = 624498
- flags = 1
- data = length 217, hash 682708ED
- sample 26:
- time = 647718
- flags = 1
- data = length 239, hash D43780FC
- sample 27:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 28:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 29:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 30:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 31:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 32:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 33:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 34:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 35:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 36:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 37:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 38:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 39:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 40:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 41:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 42:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 43:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 44:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample.mp4.1.dump
deleted file mode 100644
index 666e4b7..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample.mp4.1.dump
+++ /dev/null
@@ -1,311 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=48]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 7464
- sample count = 33
- sample 0:
- time = 322639
- flags = 1
- data = length 229, hash 2E8BA4DC
- sample 1:
- time = 345859
- flags = 1
- data = length 232, hash 22F0C510
- sample 2:
- time = 369079
- flags = 1
- data = length 235, hash 867AD0DC
- sample 3:
- time = 392299
- flags = 1
- data = length 231, hash 84E823A8
- sample 4:
- time = 415519
- flags = 1
- data = length 226, hash 1BEF3A95
- sample 5:
- time = 438739
- flags = 1
- data = length 216, hash EAA345AE
- sample 6:
- time = 461959
- flags = 1
- data = length 229, hash 6957411F
- sample 7:
- time = 485179
- flags = 1
- data = length 219, hash 41275022
- sample 8:
- time = 508399
- flags = 1
- data = length 241, hash 6495DF96
- sample 9:
- time = 531619
- flags = 1
- data = length 228, hash 63D95906
- sample 10:
- time = 554839
- flags = 1
- data = length 238, hash 34F676F9
- sample 11:
- time = 578058
- flags = 1
- data = length 234, hash E5CBC045
- sample 12:
- time = 601278
- flags = 1
- data = length 231, hash 5FC43661
- sample 13:
- time = 624498
- flags = 1
- data = length 217, hash 682708ED
- sample 14:
- time = 647718
- flags = 1
- data = length 239, hash D43780FC
- sample 15:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 16:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 17:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 18:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 19:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 20:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 21:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 22:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 23:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 24:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 25:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 26:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 27:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 28:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 29:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 30:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 31:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 32:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample.mp4.2.dump
deleted file mode 100644
index 8985adc..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample.mp4.2.dump
+++ /dev/null
@@ -1,251 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=48]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 4019
- sample count = 18
- sample 0:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 1:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 2:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 3:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 4:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 5:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 6:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 7:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 8:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 9:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 10:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 11:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 12:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 13:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 14:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 15:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 16:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 17:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample.mp4.3.dump
deleted file mode 100644
index d902c21..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample.mp4.3.dump
+++ /dev/null
@@ -1,191 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=48]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 470
- sample count = 3
- sample 0:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 1:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 2:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.0.dump
deleted file mode 100644
index 92ba157..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.0.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=758]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = 622
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7613
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 367, hash D2762FA
- sample 1:
- time = 40000
- flags = 0
- data = length 367, hash BDD3224A
- sample 2:
- time = 80000
- flags = 0
- data = length 367, hash 9302227B
- sample 3:
- time = 120000
- flags = 0
- data = length 367, hash 72996003
- sample 4:
- time = 160000
- flags = 0
- data = length 367, hash 88AE5A1B
- sample 5:
- time = 200000
- flags = 0
- data = length 367, hash E5346FE3
- sample 6:
- time = 240000
- flags = 0
- data = length 367, hash CE558362
- sample 7:
- time = 280000
- flags = 0
- data = length 367, hash 51AD3043
- sample 8:
- time = 320000
- flags = 0
- data = length 367, hash EB72E95B
- sample 9:
- time = 360000
- flags = 0
- data = length 367, hash 47F8FF23
- sample 10:
- time = 400000
- flags = 0
- data = length 367, hash 8133883D
- sample 11:
- time = 440000
- flags = 0
- data = length 495, hash E14BDFEE
- sample 12:
- time = 480000
- flags = 0
- data = length 520, hash FEE56928
- sample 13:
- time = 519999
- flags = 0
- data = length 599, hash 41F496C5
- sample 14:
- time = 560000
- flags = 0
- data = length 436, hash 76D6404
- sample 15:
- time = 600000
- flags = 0
- data = length 366, hash 56D49D4D
- sample 16:
- time = 640000
- flags = 0
- data = length 393, hash 822FC8
- sample 17:
- time = 680000
- flags = 0
- data = length 374, hash FA8AE217
- sample 18:
- time = 720000
- flags = 536870912
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.1.dump
deleted file mode 100644
index 92ba157..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.1.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=758]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = 622
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7613
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 367, hash D2762FA
- sample 1:
- time = 40000
- flags = 0
- data = length 367, hash BDD3224A
- sample 2:
- time = 80000
- flags = 0
- data = length 367, hash 9302227B
- sample 3:
- time = 120000
- flags = 0
- data = length 367, hash 72996003
- sample 4:
- time = 160000
- flags = 0
- data = length 367, hash 88AE5A1B
- sample 5:
- time = 200000
- flags = 0
- data = length 367, hash E5346FE3
- sample 6:
- time = 240000
- flags = 0
- data = length 367, hash CE558362
- sample 7:
- time = 280000
- flags = 0
- data = length 367, hash 51AD3043
- sample 8:
- time = 320000
- flags = 0
- data = length 367, hash EB72E95B
- sample 9:
- time = 360000
- flags = 0
- data = length 367, hash 47F8FF23
- sample 10:
- time = 400000
- flags = 0
- data = length 367, hash 8133883D
- sample 11:
- time = 440000
- flags = 0
- data = length 495, hash E14BDFEE
- sample 12:
- time = 480000
- flags = 0
- data = length 520, hash FEE56928
- sample 13:
- time = 519999
- flags = 0
- data = length 599, hash 41F496C5
- sample 14:
- time = 560000
- flags = 0
- data = length 436, hash 76D6404
- sample 15:
- time = 600000
- flags = 0
- data = length 366, hash 56D49D4D
- sample 16:
- time = 640000
- flags = 0
- data = length 393, hash 822FC8
- sample 17:
- time = 680000
- flags = 0
- data = length 374, hash FA8AE217
- sample 18:
- time = 720000
- flags = 536870912
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.2.dump
deleted file mode 100644
index 92ba157..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.2.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=758]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = 622
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7613
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 367, hash D2762FA
- sample 1:
- time = 40000
- flags = 0
- data = length 367, hash BDD3224A
- sample 2:
- time = 80000
- flags = 0
- data = length 367, hash 9302227B
- sample 3:
- time = 120000
- flags = 0
- data = length 367, hash 72996003
- sample 4:
- time = 160000
- flags = 0
- data = length 367, hash 88AE5A1B
- sample 5:
- time = 200000
- flags = 0
- data = length 367, hash E5346FE3
- sample 6:
- time = 240000
- flags = 0
- data = length 367, hash CE558362
- sample 7:
- time = 280000
- flags = 0
- data = length 367, hash 51AD3043
- sample 8:
- time = 320000
- flags = 0
- data = length 367, hash EB72E95B
- sample 9:
- time = 360000
- flags = 0
- data = length 367, hash 47F8FF23
- sample 10:
- time = 400000
- flags = 0
- data = length 367, hash 8133883D
- sample 11:
- time = 440000
- flags = 0
- data = length 495, hash E14BDFEE
- sample 12:
- time = 480000
- flags = 0
- data = length 520, hash FEE56928
- sample 13:
- time = 519999
- flags = 0
- data = length 599, hash 41F496C5
- sample 14:
- time = 560000
- flags = 0
- data = length 436, hash 76D6404
- sample 15:
- time = 600000
- flags = 0
- data = length 366, hash 56D49D4D
- sample 16:
- time = 640000
- flags = 0
- data = length 393, hash 822FC8
- sample 17:
- time = 680000
- flags = 0
- data = length 374, hash FA8AE217
- sample 18:
- time = 720000
- flags = 536870912
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.3.dump
deleted file mode 100644
index 92ba157..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4.3.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=758]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = 622
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7613
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 367, hash D2762FA
- sample 1:
- time = 40000
- flags = 0
- data = length 367, hash BDD3224A
- sample 2:
- time = 80000
- flags = 0
- data = length 367, hash 9302227B
- sample 3:
- time = 120000
- flags = 0
- data = length 367, hash 72996003
- sample 4:
- time = 160000
- flags = 0
- data = length 367, hash 88AE5A1B
- sample 5:
- time = 200000
- flags = 0
- data = length 367, hash E5346FE3
- sample 6:
- time = 240000
- flags = 0
- data = length 367, hash CE558362
- sample 7:
- time = 280000
- flags = 0
- data = length 367, hash 51AD3043
- sample 8:
- time = 320000
- flags = 0
- data = length 367, hash EB72E95B
- sample 9:
- time = 360000
- flags = 0
- data = length 367, hash 47F8FF23
- sample 10:
- time = 400000
- flags = 0
- data = length 367, hash 8133883D
- sample 11:
- time = 440000
- flags = 0
- data = length 495, hash E14BDFEE
- sample 12:
- time = 480000
- flags = 0
- data = length 520, hash FEE56928
- sample 13:
- time = 519999
- flags = 0
- data = length 599, hash 41F496C5
- sample 14:
- time = 560000
- flags = 0
- data = length 436, hash 76D6404
- sample 15:
- time = 600000
- flags = 0
- data = length 366, hash 56D49D4D
- sample 16:
- time = 640000
- flags = 0
- data = length 393, hash 822FC8
- sample 17:
- time = 680000
- flags = 0
- data = length 374, hash FA8AE217
- sample 18:
- time = 720000
- flags = 536870912
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump
deleted file mode 100644
index 505c85e..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=685]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7613
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 367, hash D2762FA
- sample 1:
- time = 40000
- flags = 1
- data = length 367, hash BDD3224A
- sample 2:
- time = 80000
- flags = 1
- data = length 367, hash 9302227B
- sample 3:
- time = 120000
- flags = 1
- data = length 367, hash 72996003
- sample 4:
- time = 160000
- flags = 1
- data = length 367, hash 88AE5A1B
- sample 5:
- time = 200000
- flags = 1
- data = length 367, hash E5346FE3
- sample 6:
- time = 240000
- flags = 1
- data = length 367, hash CE558362
- sample 7:
- time = 280000
- flags = 1
- data = length 367, hash 51AD3043
- sample 8:
- time = 320000
- flags = 1
- data = length 367, hash EB72E95B
- sample 9:
- time = 360000
- flags = 1
- data = length 367, hash 47F8FF23
- sample 10:
- time = 400000
- flags = 1
- data = length 367, hash 8133883D
- sample 11:
- time = 440000
- flags = 1
- data = length 495, hash E14BDFEE
- sample 12:
- time = 480000
- flags = 1
- data = length 520, hash FEE56928
- sample 13:
- time = 520000
- flags = 1
- data = length 599, hash 41F496C5
- sample 14:
- time = 560000
- flags = 1
- data = length 436, hash 76D6404
- sample 15:
- time = 600000
- flags = 1
- data = length 366, hash 56D49D4D
- sample 16:
- time = 640000
- flags = 1
- data = length 393, hash 822FC8
- sample 17:
- time = 680000
- flags = 1
- data = length 374, hash FA8AE217
- sample 18:
- time = 720000
- flags = 1
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump
deleted file mode 100644
index 8bee343..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump
+++ /dev/null
@@ -1,83 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=685]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 5411
- sample count = 13
- sample 0:
- time = 240000
- flags = 1
- data = length 367, hash CE558362
- sample 1:
- time = 280000
- flags = 1
- data = length 367, hash 51AD3043
- sample 2:
- time = 320000
- flags = 1
- data = length 367, hash EB72E95B
- sample 3:
- time = 360000
- flags = 1
- data = length 367, hash 47F8FF23
- sample 4:
- time = 400000
- flags = 1
- data = length 367, hash 8133883D
- sample 5:
- time = 440000
- flags = 1
- data = length 495, hash E14BDFEE
- sample 6:
- time = 480000
- flags = 1
- data = length 520, hash FEE56928
- sample 7:
- time = 520000
- flags = 1
- data = length 599, hash 41F496C5
- sample 8:
- time = 560000
- flags = 1
- data = length 436, hash 76D6404
- sample 9:
- time = 600000
- flags = 1
- data = length 366, hash 56D49D4D
- sample 10:
- time = 640000
- flags = 1
- data = length 393, hash 822FC8
- sample 11:
- time = 680000
- flags = 1
- data = length 374, hash FA8AE217
- sample 12:
- time = 720000
- flags = 1
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump
deleted file mode 100644
index ee1cf91..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump
+++ /dev/null
@@ -1,59 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=685]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 3081
- sample count = 7
- sample 0:
- time = 480000
- flags = 1
- data = length 520, hash FEE56928
- sample 1:
- time = 520000
- flags = 1
- data = length 599, hash 41F496C5
- sample 2:
- time = 560000
- flags = 1
- data = length 436, hash 76D6404
- sample 3:
- time = 600000
- flags = 1
- data = length 366, hash 56D49D4D
- sample 4:
- time = 640000
- flags = 1
- data = length 393, hash 822FC8
- sample 5:
- time = 680000
- flags = 1
- data = length 374, hash FA8AE217
- sample 6:
- time = 720000
- flags = 1
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump
deleted file mode 100644
index 419f044..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=685]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 393
- sample count = 1
- sample 0:
- time = 720000
- flags = 1
- data = length 393, hash 8506A1B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump
deleted file mode 100644
index 02db599..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump
+++ /dev/null
@@ -1,145 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=950]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -1683793742
- metadata = null
- initializationData:
- total output bytes = 7936
- sample count = 19
- sample 0:
- time = 0
- flags = 1073741825
- data = length 384, hash 96EFFFF3
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 1:
- time = 40000
- flags = 1073741825
- data = length 384, hash 899279C6
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 2:
- time = 80000
- flags = 1073741825
- data = length 384, hash 9EA9F45
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 3:
- time = 120000
- flags = 1073741825
- data = length 384, hash 82D362A9
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 4:
- time = 160000
- flags = 1073741825
- data = length 384, hash B8705CFB
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 5:
- time = 200000
- flags = 1073741825
- data = length 384, hash 58B5628E
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 6:
- time = 240000
- flags = 1073741825
- data = length 384, hash 87F3C13B
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 7:
- time = 280000
- flags = 1073741825
- data = length 384, hash 54333DC5
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 8:
- time = 320000
- flags = 1073741825
- data = length 384, hash 1C49C4B3
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 9:
- time = 360000
- flags = 1073741825
- data = length 384, hash 5FDC324F
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 10:
- time = 400000
- flags = 1073741825
- data = length 384, hash B2A7F444
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 11:
- time = 440000
- flags = 1073741825
- data = length 512, hash 5FD06C1E
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 12:
- time = 480000
- flags = 1073741825
- data = length 537, hash 7ABBDCB
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 13:
- time = 520000
- flags = 1073741825
- data = length 616, hash 3F657E23
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 14:
- time = 560000
- flags = 1073741825
- data = length 453, hash 8FCF0529
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 15:
- time = 600000
- flags = 1073741825
- data = length 383, hash 7F8C9E19
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 16:
- time = 640000
- flags = 1073741825
- data = length 410, hash 3727858D
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 17:
- time = 680000
- flags = 1073741825
- data = length 391, hash E2931212
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 18:
- time = 720000
- flags = 1073741825
- data = length 410, hash 63017D46
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump
deleted file mode 100644
index 8b45dd0..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump
+++ /dev/null
@@ -1,109 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=950]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -1683793742
- metadata = null
- initializationData:
- total output bytes = 5632
- sample count = 13
- sample 0:
- time = 240000
- flags = 1073741825
- data = length 384, hash 87F3C13B
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 1:
- time = 280000
- flags = 1073741825
- data = length 384, hash 54333DC5
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 2:
- time = 320000
- flags = 1073741825
- data = length 384, hash 1C49C4B3
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 3:
- time = 360000
- flags = 1073741825
- data = length 384, hash 5FDC324F
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 4:
- time = 400000
- flags = 1073741825
- data = length 384, hash B2A7F444
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 5:
- time = 440000
- flags = 1073741825
- data = length 512, hash 5FD06C1E
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 6:
- time = 480000
- flags = 1073741825
- data = length 537, hash 7ABBDCB
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 7:
- time = 520000
- flags = 1073741825
- data = length 616, hash 3F657E23
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 8:
- time = 560000
- flags = 1073741825
- data = length 453, hash 8FCF0529
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 9:
- time = 600000
- flags = 1073741825
- data = length 383, hash 7F8C9E19
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 10:
- time = 640000
- flags = 1073741825
- data = length 410, hash 3727858D
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 11:
- time = 680000
- flags = 1073741825
- data = length 391, hash E2931212
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 12:
- time = 720000
- flags = 1073741825
- data = length 410, hash 63017D46
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump
deleted file mode 100644
index a6be34d..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump
+++ /dev/null
@@ -1,73 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=950]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -1683793742
- metadata = null
- initializationData:
- total output bytes = 3200
- sample count = 7
- sample 0:
- time = 480000
- flags = 1073741825
- data = length 537, hash 7ABBDCB
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 1:
- time = 520000
- flags = 1073741825
- data = length 616, hash 3F657E23
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 2:
- time = 560000
- flags = 1073741825
- data = length 453, hash 8FCF0529
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 3:
- time = 600000
- flags = 1073741825
- data = length 383, hash 7F8C9E19
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 4:
- time = 640000
- flags = 1073741825
- data = length 410, hash 3727858D
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 5:
- time = 680000
- flags = 1073741825
- data = length 391, hash E2931212
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
- sample 6:
- time = 720000
- flags = 1073741825
- data = length 410, hash 63017D46
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump
deleted file mode 100644
index 08fce46..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump
+++ /dev/null
@@ -1,37 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 760000
- getPosition(0) = [[timeUs=0, position=950]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -1683793742
- metadata = null
- initializationData:
- total output bytes = 410
- sample count = 1
- sample 0:
- time = 720000
- flags = 1073741825
- data = length 410, hash 63017D46
- crypto mode = 1
- encryption key = length 16, hash 9FDDEA52
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump
deleted file mode 100644
index 2880b94..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 526000
- getPosition(0) = [[timeUs=0, position=1161]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 34686
- width = 1280
- height = 720
- frameRate = 13.307984
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[mdta: key=com.android.capture.fps]
- initializationData:
- data = length 22, hash 4CF81805
- data = length 9, hash FBAFBA1C
- total output bytes = 42320
- sample count = 7
- sample 0:
- time = 0
- flags = 1
- data = length 34656, hash D92B66FF
- sample 1:
- time = 325344
- flags = 0
- data = length 768, hash D0C3B229
- sample 2:
- time = 358677
- flags = 0
- data = length 1184, hash C598EFC0
- sample 3:
- time = 392011
- flags = 0
- data = length 576, hash 667AEC2C
- sample 4:
- time = 425344
- flags = 0
- data = length 1456, hash 430D1498
- sample 5:
- time = 458677
- flags = 0
- data = length 1280, hash 12267E0E
- sample 6:
- time = 492011
- flags = 536870912
- data = length 2400, hash FBCB42C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump
deleted file mode 100644
index 2880b94..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 526000
- getPosition(0) = [[timeUs=0, position=1161]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 34686
- width = 1280
- height = 720
- frameRate = 13.307984
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[mdta: key=com.android.capture.fps]
- initializationData:
- data = length 22, hash 4CF81805
- data = length 9, hash FBAFBA1C
- total output bytes = 42320
- sample count = 7
- sample 0:
- time = 0
- flags = 1
- data = length 34656, hash D92B66FF
- sample 1:
- time = 325344
- flags = 0
- data = length 768, hash D0C3B229
- sample 2:
- time = 358677
- flags = 0
- data = length 1184, hash C598EFC0
- sample 3:
- time = 392011
- flags = 0
- data = length 576, hash 667AEC2C
- sample 4:
- time = 425344
- flags = 0
- data = length 1456, hash 430D1498
- sample 5:
- time = 458677
- flags = 0
- data = length 1280, hash 12267E0E
- sample 6:
- time = 492011
- flags = 536870912
- data = length 2400, hash FBCB42C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump
deleted file mode 100644
index 2880b94..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 526000
- getPosition(0) = [[timeUs=0, position=1161]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 34686
- width = 1280
- height = 720
- frameRate = 13.307984
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[mdta: key=com.android.capture.fps]
- initializationData:
- data = length 22, hash 4CF81805
- data = length 9, hash FBAFBA1C
- total output bytes = 42320
- sample count = 7
- sample 0:
- time = 0
- flags = 1
- data = length 34656, hash D92B66FF
- sample 1:
- time = 325344
- flags = 0
- data = length 768, hash D0C3B229
- sample 2:
- time = 358677
- flags = 0
- data = length 1184, hash C598EFC0
- sample 3:
- time = 392011
- flags = 0
- data = length 576, hash 667AEC2C
- sample 4:
- time = 425344
- flags = 0
- data = length 1456, hash 430D1498
- sample 5:
- time = 458677
- flags = 0
- data = length 1280, hash 12267E0E
- sample 6:
- time = 492011
- flags = 536870912
- data = length 2400, hash FBCB42C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump
deleted file mode 100644
index 2880b94..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 526000
- getPosition(0) = [[timeUs=0, position=1161]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 34686
- width = 1280
- height = 720
- frameRate = 13.307984
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = entries=[mdta: key=com.android.capture.fps]
- initializationData:
- data = length 22, hash 4CF81805
- data = length 9, hash FBAFBA1C
- total output bytes = 42320
- sample count = 7
- sample 0:
- time = 0
- flags = 1
- data = length 34656, hash D92B66FF
- sample 1:
- time = 325344
- flags = 0
- data = length 768, hash D0C3B229
- sample 2:
- time = 358677
- flags = 0
- data = length 1184, hash C598EFC0
- sample 3:
- time = 392011
- flags = 0
- data = length 576, hash 667AEC2C
- sample 4:
- time = 425344
- flags = 0
- data = length 1456, hash 430D1498
- sample 5:
- time = 458677
- flags = 0
- data = length 1280, hash 12267E0E
- sample 6:
- time = 492011
- flags = 536870912
- data = length 2400, hash FBCB42C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented.mp4.0.dump
deleted file mode 100644
index 65f59d7..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented.mp4.0.dump
+++ /dev/null
@@ -1,363 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=1828]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 18257
- sample count = 46
- sample 0:
- time = 0
- flags = 1
- data = length 18, hash 96519432
- sample 1:
- time = 23000
- flags = 1
- data = length 4, hash EE9DF
- sample 2:
- time = 46000
- flags = 1
- data = length 4, hash EEDBF
- sample 3:
- time = 69000
- flags = 1
- data = length 157, hash E2F078F4
- sample 4:
- time = 92000
- flags = 1
- data = length 371, hash B9471F94
- sample 5:
- time = 116000
- flags = 1
- data = length 373, hash 2AB265CB
- sample 6:
- time = 139000
- flags = 1
- data = length 402, hash 1295477C
- sample 7:
- time = 162000
- flags = 1
- data = length 455, hash 2D8146C8
- sample 8:
- time = 185000
- flags = 1
- data = length 434, hash F2C5D287
- sample 9:
- time = 208000
- flags = 1
- data = length 450, hash 84143FCD
- sample 10:
- time = 232000
- flags = 1
- data = length 429, hash EF769D50
- sample 11:
- time = 255000
- flags = 1
- data = length 450, hash EC3DE692
- sample 12:
- time = 278000
- flags = 1
- data = length 447, hash 3E519E13
- sample 13:
- time = 301000
- flags = 1
- data = length 457, hash 1E4F23A0
- sample 14:
- time = 325000
- flags = 1
- data = length 447, hash A439EA97
- sample 15:
- time = 348000
- flags = 1
- data = length 456, hash 1E9034C6
- sample 16:
- time = 371000
- flags = 1
- data = length 398, hash 99DB7345
- sample 17:
- time = 394000
- flags = 1
- data = length 474, hash 3F05F10A
- sample 18:
- time = 417000
- flags = 1
- data = length 416, hash C105EE09
- sample 19:
- time = 441000
- flags = 1
- data = length 454, hash 5FDBE458
- sample 20:
- time = 464000
- flags = 1
- data = length 438, hash 41A93AC3
- sample 21:
- time = 487000
- flags = 1
- data = length 443, hash 10FDA652
- sample 22:
- time = 510000
- flags = 1
- data = length 412, hash 1F791E25
- sample 23:
- time = 534000
- flags = 1
- data = length 482, hash A6D983D
- sample 24:
- time = 557000
- flags = 1
- data = length 386, hash BED7392F
- sample 25:
- time = 580000
- flags = 1
- data = length 463, hash 5309F8C9
- sample 26:
- time = 603000
- flags = 1
- data = length 394, hash 21C7321F
- sample 27:
- time = 626000
- flags = 1
- data = length 489, hash 71B4730D
- sample 28:
- time = 650000
- flags = 1
- data = length 403, hash D9C6DE89
- sample 29:
- time = 673000
- flags = 1
- data = length 447, hash 9B14B73B
- sample 30:
- time = 696000
- flags = 1
- data = length 439, hash 4760D35B
- sample 31:
- time = 719000
- flags = 1
- data = length 463, hash 1601F88D
- sample 32:
- time = 743000
- flags = 1
- data = length 423, hash D4AE6773
- sample 33:
- time = 766000
- flags = 1
- data = length 497, hash A3C674D3
- sample 34:
- time = 789000
- flags = 1
- data = length 419, hash D3734A1F
- sample 35:
- time = 812000
- flags = 1
- data = length 474, hash DFB41F9
- sample 36:
- time = 835000
- flags = 1
- data = length 413, hash 53E7CB9F
- sample 37:
- time = 859000
- flags = 1
- data = length 445, hash D15B0E39
- sample 38:
- time = 882000
- flags = 1
- data = length 453, hash 77ED81E4
- sample 39:
- time = 905000
- flags = 1
- data = length 545, hash 3321AEB9
- sample 40:
- time = 928000
- flags = 1
- data = length 317, hash F557D0E
- sample 41:
- time = 952000
- flags = 1
- data = length 537, hash ED58CF7B
- sample 42:
- time = 975000
- flags = 1
- data = length 458, hash 51CDAA10
- sample 43:
- time = 998000
- flags = 1
- data = length 465, hash CBA1EFD7
- sample 44:
- time = 1021000
- flags = 1
- data = length 446, hash D6735B8A
- sample 45:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
deleted file mode 100644
index 27838bd..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
+++ /dev/null
@@ -1,363 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1067733
- getPosition(0) = [[timeUs=66733, position=1325]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 18257
- sample count = 46
- sample 0:
- time = 0
- flags = 1
- data = length 18, hash 96519432
- sample 1:
- time = 23000
- flags = 1
- data = length 4, hash EE9DF
- sample 2:
- time = 46000
- flags = 1
- data = length 4, hash EEDBF
- sample 3:
- time = 69000
- flags = 1
- data = length 157, hash E2F078F4
- sample 4:
- time = 92000
- flags = 1
- data = length 371, hash B9471F94
- sample 5:
- time = 116000
- flags = 1
- data = length 373, hash 2AB265CB
- sample 6:
- time = 139000
- flags = 1
- data = length 402, hash 1295477C
- sample 7:
- time = 162000
- flags = 1
- data = length 455, hash 2D8146C8
- sample 8:
- time = 185000
- flags = 1
- data = length 434, hash F2C5D287
- sample 9:
- time = 208000
- flags = 1
- data = length 450, hash 84143FCD
- sample 10:
- time = 232000
- flags = 1
- data = length 429, hash EF769D50
- sample 11:
- time = 255000
- flags = 1
- data = length 450, hash EC3DE692
- sample 12:
- time = 278000
- flags = 1
- data = length 447, hash 3E519E13
- sample 13:
- time = 301000
- flags = 1
- data = length 457, hash 1E4F23A0
- sample 14:
- time = 325000
- flags = 1
- data = length 447, hash A439EA97
- sample 15:
- time = 348000
- flags = 1
- data = length 456, hash 1E9034C6
- sample 16:
- time = 371000
- flags = 1
- data = length 398, hash 99DB7345
- sample 17:
- time = 394000
- flags = 1
- data = length 474, hash 3F05F10A
- sample 18:
- time = 417000
- flags = 1
- data = length 416, hash C105EE09
- sample 19:
- time = 441000
- flags = 1
- data = length 454, hash 5FDBE458
- sample 20:
- time = 464000
- flags = 1
- data = length 438, hash 41A93AC3
- sample 21:
- time = 487000
- flags = 1
- data = length 443, hash 10FDA652
- sample 22:
- time = 510000
- flags = 1
- data = length 412, hash 1F791E25
- sample 23:
- time = 534000
- flags = 1
- data = length 482, hash A6D983D
- sample 24:
- time = 557000
- flags = 1
- data = length 386, hash BED7392F
- sample 25:
- time = 580000
- flags = 1
- data = length 463, hash 5309F8C9
- sample 26:
- time = 603000
- flags = 1
- data = length 394, hash 21C7321F
- sample 27:
- time = 626000
- flags = 1
- data = length 489, hash 71B4730D
- sample 28:
- time = 650000
- flags = 1
- data = length 403, hash D9C6DE89
- sample 29:
- time = 673000
- flags = 1
- data = length 447, hash 9B14B73B
- sample 30:
- time = 696000
- flags = 1
- data = length 439, hash 4760D35B
- sample 31:
- time = 719000
- flags = 1
- data = length 463, hash 1601F88D
- sample 32:
- time = 743000
- flags = 1
- data = length 423, hash D4AE6773
- sample 33:
- time = 766000
- flags = 1
- data = length 497, hash A3C674D3
- sample 34:
- time = 789000
- flags = 1
- data = length 419, hash D3734A1F
- sample 35:
- time = 812000
- flags = 1
- data = length 474, hash DFB41F9
- sample 36:
- time = 835000
- flags = 1
- data = length 413, hash 53E7CB9F
- sample 37:
- time = 859000
- flags = 1
- data = length 445, hash D15B0E39
- sample 38:
- time = 882000
- flags = 1
- data = length 453, hash 77ED81E4
- sample 39:
- time = 905000
- flags = 1
- data = length 545, hash 3321AEB9
- sample 40:
- time = 928000
- flags = 1
- data = length 317, hash F557D0E
- sample 41:
- time = 952000
- flags = 1
- data = length 537, hash ED58CF7B
- sample 42:
- time = 975000
- flags = 1
- data = length 458, hash 51CDAA10
- sample 43:
- time = 998000
- flags = 1
- data = length 465, hash CBA1EFD7
- sample 44:
- time = 1021000
- flags = 1
- data = length 446, hash D6735B8A
- sample 45:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
deleted file mode 100644
index ea6deaf..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
+++ /dev/null
@@ -1,303 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1067733
- getPosition(0) = [[timeUs=66733, position=1325]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 13359
- sample count = 31
- sample 0:
- time = 348000
- flags = 1
- data = length 456, hash 1E9034C6
- sample 1:
- time = 371000
- flags = 1
- data = length 398, hash 99DB7345
- sample 2:
- time = 394000
- flags = 1
- data = length 474, hash 3F05F10A
- sample 3:
- time = 417000
- flags = 1
- data = length 416, hash C105EE09
- sample 4:
- time = 441000
- flags = 1
- data = length 454, hash 5FDBE458
- sample 5:
- time = 464000
- flags = 1
- data = length 438, hash 41A93AC3
- sample 6:
- time = 487000
- flags = 1
- data = length 443, hash 10FDA652
- sample 7:
- time = 510000
- flags = 1
- data = length 412, hash 1F791E25
- sample 8:
- time = 534000
- flags = 1
- data = length 482, hash A6D983D
- sample 9:
- time = 557000
- flags = 1
- data = length 386, hash BED7392F
- sample 10:
- time = 580000
- flags = 1
- data = length 463, hash 5309F8C9
- sample 11:
- time = 603000
- flags = 1
- data = length 394, hash 21C7321F
- sample 12:
- time = 626000
- flags = 1
- data = length 489, hash 71B4730D
- sample 13:
- time = 650000
- flags = 1
- data = length 403, hash D9C6DE89
- sample 14:
- time = 673000
- flags = 1
- data = length 447, hash 9B14B73B
- sample 15:
- time = 696000
- flags = 1
- data = length 439, hash 4760D35B
- sample 16:
- time = 719000
- flags = 1
- data = length 463, hash 1601F88D
- sample 17:
- time = 743000
- flags = 1
- data = length 423, hash D4AE6773
- sample 18:
- time = 766000
- flags = 1
- data = length 497, hash A3C674D3
- sample 19:
- time = 789000
- flags = 1
- data = length 419, hash D3734A1F
- sample 20:
- time = 812000
- flags = 1
- data = length 474, hash DFB41F9
- sample 21:
- time = 835000
- flags = 1
- data = length 413, hash 53E7CB9F
- sample 22:
- time = 859000
- flags = 1
- data = length 445, hash D15B0E39
- sample 23:
- time = 882000
- flags = 1
- data = length 453, hash 77ED81E4
- sample 24:
- time = 905000
- flags = 1
- data = length 545, hash 3321AEB9
- sample 25:
- time = 928000
- flags = 1
- data = length 317, hash F557D0E
- sample 26:
- time = 952000
- flags = 1
- data = length 537, hash ED58CF7B
- sample 27:
- time = 975000
- flags = 1
- data = length 458, hash 51CDAA10
- sample 28:
- time = 998000
- flags = 1
- data = length 465, hash CBA1EFD7
- sample 29:
- time = 1021000
- flags = 1
- data = length 446, hash D6735B8A
- sample 30:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
deleted file mode 100644
index d14025e..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
+++ /dev/null
@@ -1,243 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1067733
- getPosition(0) = [[timeUs=66733, position=1325]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 6804
- sample count = 16
- sample 0:
- time = 696000
- flags = 1
- data = length 439, hash 4760D35B
- sample 1:
- time = 719000
- flags = 1
- data = length 463, hash 1601F88D
- sample 2:
- time = 743000
- flags = 1
- data = length 423, hash D4AE6773
- sample 3:
- time = 766000
- flags = 1
- data = length 497, hash A3C674D3
- sample 4:
- time = 789000
- flags = 1
- data = length 419, hash D3734A1F
- sample 5:
- time = 812000
- flags = 1
- data = length 474, hash DFB41F9
- sample 6:
- time = 835000
- flags = 1
- data = length 413, hash 53E7CB9F
- sample 7:
- time = 859000
- flags = 1
- data = length 445, hash D15B0E39
- sample 8:
- time = 882000
- flags = 1
- data = length 453, hash 77ED81E4
- sample 9:
- time = 905000
- flags = 1
- data = length 545, hash 3321AEB9
- sample 10:
- time = 928000
- flags = 1
- data = length 317, hash F557D0E
- sample 11:
- time = 952000
- flags = 1
- data = length 537, hash ED58CF7B
- sample 12:
- time = 975000
- flags = 1
- data = length 458, hash 51CDAA10
- sample 13:
- time = 998000
- flags = 1
- data = length 465, hash CBA1EFD7
- sample 14:
- time = 1021000
- flags = 1
- data = length 446, hash D6735B8A
- sample 15:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
deleted file mode 100644
index d08a1e9..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
+++ /dev/null
@@ -1,183 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1067733
- getPosition(0) = [[timeUs=66733, position=1325]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 10
- sample count = 1
- sample 0:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump
deleted file mode 100644
index d596a77..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump
+++ /dev/null
@@ -1,388 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=1828]]
-numberOfTracks = 3
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = -1
- width = 1080
- height = 720
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 85933
- sample count = 30
- sample 0:
- time = 66000
- flags = 1
- data = length 38070, hash B58E1AEE
- sample 1:
- time = 199000
- flags = 0
- data = length 8340, hash 8AC449FF
- sample 2:
- time = 132000
- flags = 0
- data = length 1295, hash C0DA5090
- sample 3:
- time = 100000
- flags = 0
- data = length 469, hash D6E0A200
- sample 4:
- time = 166000
- flags = 0
- data = length 564, hash E5F56C5B
- sample 5:
- time = 332000
- flags = 0
- data = length 6075, hash 8756E49E
- sample 6:
- time = 266000
- flags = 0
- data = length 847, hash DCC2B618
- sample 7:
- time = 233000
- flags = 0
- data = length 455, hash B9CCE047
- sample 8:
- time = 299000
- flags = 0
- data = length 467, hash 69806D94
- sample 9:
- time = 466000
- flags = 0
- data = length 4549, hash 3944F501
- sample 10:
- time = 399000
- flags = 0
- data = length 1087, hash 491BF106
- sample 11:
- time = 367000
- flags = 0
- data = length 380, hash 5FED016A
- sample 12:
- time = 433000
- flags = 0
- data = length 455, hash 8A0610
- sample 13:
- time = 599000
- flags = 0
- data = length 5190, hash B9031D8
- sample 14:
- time = 533000
- flags = 0
- data = length 1071, hash 684E7DC8
- sample 15:
- time = 500000
- flags = 0
- data = length 653, hash 8494F326
- sample 16:
- time = 566000
- flags = 0
- data = length 485, hash 2CCC85F4
- sample 17:
- time = 733000
- flags = 0
- data = length 4884, hash D16B6A96
- sample 18:
- time = 666000
- flags = 0
- data = length 997, hash 164FF210
- sample 19:
- time = 633000
- flags = 0
- data = length 640, hash F664125B
- sample 20:
- time = 700000
- flags = 0
- data = length 491, hash B5930C7C
- sample 21:
- time = 866000
- flags = 0
- data = length 2989, hash 92CF4FCF
- sample 22:
- time = 800000
- flags = 0
- data = length 838, hash 294A3451
- sample 23:
- time = 767000
- flags = 0
- data = length 544, hash FCCE2DE6
- sample 24:
- time = 833000
- flags = 0
- data = length 329, hash A654FFA1
- sample 25:
- time = 1000000
- flags = 0
- data = length 1517, hash 5F7EBF8B
- sample 26:
- time = 933000
- flags = 0
- data = length 803, hash 7A5C4C1D
- sample 27:
- time = 900000
- flags = 0
- data = length 415, hash B31BBC3B
- sample 28:
- time = 967000
- flags = 0
- data = length 415, hash 850DFEA3
- sample 29:
- time = 1033000
- flags = 0
- data = length 619, hash AB5E56CA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- data = length 5, hash 2B7623A
- total output bytes = 18257
- sample count = 46
- sample 0:
- time = 0
- flags = 1
- data = length 18, hash 96519432
- sample 1:
- time = 23000
- flags = 1
- data = length 4, hash EE9DF
- sample 2:
- time = 46000
- flags = 1
- data = length 4, hash EEDBF
- sample 3:
- time = 69000
- flags = 1
- data = length 157, hash E2F078F4
- sample 4:
- time = 92000
- flags = 1
- data = length 371, hash B9471F94
- sample 5:
- time = 116000
- flags = 1
- data = length 373, hash 2AB265CB
- sample 6:
- time = 139000
- flags = 1
- data = length 402, hash 1295477C
- sample 7:
- time = 162000
- flags = 1
- data = length 455, hash 2D8146C8
- sample 8:
- time = 185000
- flags = 1
- data = length 434, hash F2C5D287
- sample 9:
- time = 208000
- flags = 1
- data = length 450, hash 84143FCD
- sample 10:
- time = 232000
- flags = 1
- data = length 429, hash EF769D50
- sample 11:
- time = 255000
- flags = 1
- data = length 450, hash EC3DE692
- sample 12:
- time = 278000
- flags = 1
- data = length 447, hash 3E519E13
- sample 13:
- time = 301000
- flags = 1
- data = length 457, hash 1E4F23A0
- sample 14:
- time = 325000
- flags = 1
- data = length 447, hash A439EA97
- sample 15:
- time = 348000
- flags = 1
- data = length 456, hash 1E9034C6
- sample 16:
- time = 371000
- flags = 1
- data = length 398, hash 99DB7345
- sample 17:
- time = 394000
- flags = 1
- data = length 474, hash 3F05F10A
- sample 18:
- time = 417000
- flags = 1
- data = length 416, hash C105EE09
- sample 19:
- time = 441000
- flags = 1
- data = length 454, hash 5FDBE458
- sample 20:
- time = 464000
- flags = 1
- data = length 438, hash 41A93AC3
- sample 21:
- time = 487000
- flags = 1
- data = length 443, hash 10FDA652
- sample 22:
- time = 510000
- flags = 1
- data = length 412, hash 1F791E25
- sample 23:
- time = 534000
- flags = 1
- data = length 482, hash A6D983D
- sample 24:
- time = 557000
- flags = 1
- data = length 386, hash BED7392F
- sample 25:
- time = 580000
- flags = 1
- data = length 463, hash 5309F8C9
- sample 26:
- time = 603000
- flags = 1
- data = length 394, hash 21C7321F
- sample 27:
- time = 626000
- flags = 1
- data = length 489, hash 71B4730D
- sample 28:
- time = 650000
- flags = 1
- data = length 403, hash D9C6DE89
- sample 29:
- time = 673000
- flags = 1
- data = length 447, hash 9B14B73B
- sample 30:
- time = 696000
- flags = 1
- data = length 439, hash 4760D35B
- sample 31:
- time = 719000
- flags = 1
- data = length 463, hash 1601F88D
- sample 32:
- time = 743000
- flags = 1
- data = length 423, hash D4AE6773
- sample 33:
- time = 766000
- flags = 1
- data = length 497, hash A3C674D3
- sample 34:
- time = 789000
- flags = 1
- data = length 419, hash D3734A1F
- sample 35:
- time = 812000
- flags = 1
- data = length 474, hash DFB41F9
- sample 36:
- time = 835000
- flags = 1
- data = length 413, hash 53E7CB9F
- sample 37:
- time = 859000
- flags = 1
- data = length 445, hash D15B0E39
- sample 38:
- time = 882000
- flags = 1
- data = length 453, hash 77ED81E4
- sample 39:
- time = 905000
- flags = 1
- data = length 545, hash 3321AEB9
- sample 40:
- time = 928000
- flags = 1
- data = length 317, hash F557D0E
- sample 41:
- time = 952000
- flags = 1
- data = length 537, hash ED58CF7B
- sample 42:
- time = 975000
- flags = 1
- data = length 458, hash 51CDAA10
- sample 43:
- time = 998000
- flags = 1
- data = length 465, hash CBA1EFD7
- sample 44:
- time = 1021000
- flags = 1
- data = length 446, hash D6735B8A
- sample 45:
- time = 1044000
- flags = 1
- data = length 10, hash A453EEBE
-track 3:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump b/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump
deleted file mode 100644
index 3b1c90c..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump
+++ /dev/null
@@ -1,359 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=2192]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 9529
- sample count = 45
- sample 0:
- time = 44000
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 67219
- flags = 1
- data = length 6, hash 31EC5206
- sample 2:
- time = 90439
- flags = 1
- data = length 148, hash 894A176B
- sample 3:
- time = 113659
- flags = 1
- data = length 189, hash CEF235A1
- sample 4:
- time = 136879
- flags = 1
- data = length 205, hash BBF5F7B0
- sample 5:
- time = 160099
- flags = 1
- data = length 210, hash F278B193
- sample 6:
- time = 183319
- flags = 1
- data = length 210, hash 82DA1589
- sample 7:
- time = 206539
- flags = 1
- data = length 207, hash 5BE231DF
- sample 8:
- time = 229759
- flags = 1
- data = length 225, hash 18819EE1
- sample 9:
- time = 252979
- flags = 1
- data = length 215, hash CA7FA67B
- sample 10:
- time = 276199
- flags = 1
- data = length 211, hash 581A1C18
- sample 11:
- time = 299419
- flags = 1
- data = length 216, hash ADB88187
- sample 12:
- time = 322639
- flags = 1
- data = length 229, hash 2E8BA4DC
- sample 13:
- time = 345859
- flags = 1
- data = length 232, hash 22F0C510
- sample 14:
- time = 369079
- flags = 1
- data = length 235, hash 867AD0DC
- sample 15:
- time = 392299
- flags = 1
- data = length 231, hash 84E823A8
- sample 16:
- time = 415519
- flags = 1
- data = length 226, hash 1BEF3A95
- sample 17:
- time = 438739
- flags = 1
- data = length 216, hash EAA345AE
- sample 18:
- time = 461959
- flags = 1
- data = length 229, hash 6957411F
- sample 19:
- time = 485179
- flags = 1
- data = length 219, hash 41275022
- sample 20:
- time = 508399
- flags = 1
- data = length 241, hash 6495DF96
- sample 21:
- time = 531619
- flags = 1
- data = length 228, hash 63D95906
- sample 22:
- time = 554839
- flags = 1
- data = length 238, hash 34F676F9
- sample 23:
- time = 578058
- flags = 1
- data = length 234, hash E5CBC045
- sample 24:
- time = 601278
- flags = 1
- data = length 231, hash 5FC43661
- sample 25:
- time = 624498
- flags = 1
- data = length 217, hash 682708ED
- sample 26:
- time = 647718
- flags = 1
- data = length 239, hash D43780FC
- sample 27:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 28:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 29:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 30:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 31:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 32:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 33:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 34:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 35:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 36:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 37:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 38:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 39:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 40:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 41:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 42:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 43:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 44:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump b/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump
deleted file mode 100644
index 7d8c3c1..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump
+++ /dev/null
@@ -1,311 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=2192]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 7464
- sample count = 33
- sample 0:
- time = 322639
- flags = 1
- data = length 229, hash 2E8BA4DC
- sample 1:
- time = 345859
- flags = 1
- data = length 232, hash 22F0C510
- sample 2:
- time = 369079
- flags = 1
- data = length 235, hash 867AD0DC
- sample 3:
- time = 392299
- flags = 1
- data = length 231, hash 84E823A8
- sample 4:
- time = 415519
- flags = 1
- data = length 226, hash 1BEF3A95
- sample 5:
- time = 438739
- flags = 1
- data = length 216, hash EAA345AE
- sample 6:
- time = 461959
- flags = 1
- data = length 229, hash 6957411F
- sample 7:
- time = 485179
- flags = 1
- data = length 219, hash 41275022
- sample 8:
- time = 508399
- flags = 1
- data = length 241, hash 6495DF96
- sample 9:
- time = 531619
- flags = 1
- data = length 228, hash 63D95906
- sample 10:
- time = 554839
- flags = 1
- data = length 238, hash 34F676F9
- sample 11:
- time = 578058
- flags = 1
- data = length 234, hash E5CBC045
- sample 12:
- time = 601278
- flags = 1
- data = length 231, hash 5FC43661
- sample 13:
- time = 624498
- flags = 1
- data = length 217, hash 682708ED
- sample 14:
- time = 647718
- flags = 1
- data = length 239, hash D43780FC
- sample 15:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 16:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 17:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 18:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 19:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 20:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 21:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 22:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 23:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 24:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 25:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 26:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 27:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 28:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 29:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 30:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 31:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 32:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump b/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump
deleted file mode 100644
index a87f167..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump
+++ /dev/null
@@ -1,251 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=2192]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 4019
- sample count = 18
- sample 0:
- time = 670938
- flags = 1
- data = length 243, hash C5E17980
- sample 1:
- time = 694158
- flags = 1
- data = length 231, hash AC5837BA
- sample 2:
- time = 717378
- flags = 1
- data = length 230, hash 169EE895
- sample 3:
- time = 740598
- flags = 1
- data = length 238, hash C48FF3F1
- sample 4:
- time = 763818
- flags = 1
- data = length 225, hash 531E4599
- sample 5:
- time = 787038
- flags = 1
- data = length 232, hash CB3C6B8D
- sample 6:
- time = 810258
- flags = 1
- data = length 243, hash F8C94C7
- sample 7:
- time = 833478
- flags = 1
- data = length 232, hash A646A7D0
- sample 8:
- time = 856698
- flags = 1
- data = length 237, hash E8B787A5
- sample 9:
- time = 879918
- flags = 1
- data = length 228, hash 3FA7A29F
- sample 10:
- time = 903138
- flags = 1
- data = length 235, hash B9B33B0A
- sample 11:
- time = 926358
- flags = 1
- data = length 264, hash 71A4869E
- sample 12:
- time = 949578
- flags = 1
- data = length 257, hash D049B54C
- sample 13:
- time = 972798
- flags = 1
- data = length 227, hash 66757231
- sample 14:
- time = 996018
- flags = 1
- data = length 227, hash BD374F1B
- sample 15:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 16:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 17:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump b/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump
deleted file mode 100644
index 6431ca9..0000000
--- a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump
+++ /dev/null
@@ -1,191 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1024000
- getPosition(0) = [[timeUs=0, position=2192]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = video/avc
- maxInputSize = 36722
- width = 1080
- height = 720
- frameRate = 29.970028
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 29, hash 4746B5D9
- data = length 10, hash 7A0D0F2B
- total output bytes = 89876
- sample count = 30
- sample 0:
- time = 0
- flags = 1
- data = length 36692, hash D216076E
- sample 1:
- time = 66733
- flags = 0
- data = length 5312, hash D45D3CA0
- sample 2:
- time = 33366
- flags = 0
- data = length 599, hash 1BE7812D
- sample 3:
- time = 200200
- flags = 0
- data = length 7735, hash 4490F110
- sample 4:
- time = 133466
- flags = 0
- data = length 987, hash 560B5036
- sample 5:
- time = 100100
- flags = 0
- data = length 673, hash ED7CD8C7
- sample 6:
- time = 166833
- flags = 0
- data = length 523, hash 3020DF50
- sample 7:
- time = 333666
- flags = 0
- data = length 6061, hash 736C72B2
- sample 8:
- time = 266933
- flags = 0
- data = length 992, hash FE132F23
- sample 9:
- time = 233566
- flags = 0
- data = length 623, hash 5B2C1816
- sample 10:
- time = 300300
- flags = 0
- data = length 421, hash 742E69C1
- sample 11:
- time = 433766
- flags = 0
- data = length 4899, hash F72F86A1
- sample 12:
- time = 400400
- flags = 0
- data = length 568, hash 519A8E50
- sample 13:
- time = 367033
- flags = 0
- data = length 620, hash 3990AA39
- sample 14:
- time = 567233
- flags = 0
- data = length 5450, hash F06EC4AA
- sample 15:
- time = 500500
- flags = 0
- data = length 1051, hash 92DFA63A
- sample 16:
- time = 467133
- flags = 0
- data = length 874, hash 69587FB4
- sample 17:
- time = 533866
- flags = 0
- data = length 781, hash 36BE495B
- sample 18:
- time = 700700
- flags = 0
- data = length 4725, hash AC0C8CD3
- sample 19:
- time = 633966
- flags = 0
- data = length 1022, hash 5D8BFF34
- sample 20:
- time = 600600
- flags = 0
- data = length 790, hash 99413A99
- sample 21:
- time = 667333
- flags = 0
- data = length 610, hash 5E129290
- sample 22:
- time = 834166
- flags = 0
- data = length 2751, hash 769974CB
- sample 23:
- time = 767433
- flags = 0
- data = length 745, hash B78A477A
- sample 24:
- time = 734066
- flags = 0
- data = length 621, hash CF741E7A
- sample 25:
- time = 800800
- flags = 0
- data = length 505, hash 1DB4894E
- sample 26:
- time = 967633
- flags = 0
- data = length 1268, hash C15348DC
- sample 27:
- time = 900900
- flags = 0
- data = length 880, hash C2DE85D0
- sample 28:
- time = 867533
- flags = 0
- data = length 530, hash C98BC6A8
- sample 29:
- time = 934266
- flags = 536870912
- data = length 568, hash 4FE5C8EA
-track 1:
- format:
- bitrate = -1
- id = 2
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = 294
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 470
- sample count = 3
- sample 0:
- time = 1019238
- flags = 1
- data = length 235, hash 999477F6
- sample 1:
- time = 1042458
- flags = 1
- data = length 229, hash FFF98DF0
- sample 2:
- time = 1065678
- flags = 536870913
- data = length 6, hash 31B22286
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus.0.dump b/tree/library/extractor/src/test/assets/ogg/bear.opus.0.dump
deleted file mode 100644
index 4207420..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear.opus.0.dump
+++ /dev/null
@@ -1,1134 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2747500
- getPosition(0) = [[timeUs=0, position=125]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/opus
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 19, hash BFE794DB
- data = length 8, hash CA22068C
- data = length 8, hash 79C07075
- total output bytes = 25541
- sample count = 275
- sample 0:
- time = 0
- flags = 1
- data = length 234, hash B77BFFDA
- sample 1:
- time = 10000
- flags = 1
- data = length 165, hash F7B07C35
- sample 2:
- time = 20000
- flags = 1
- data = length 100, hash 21AFA81F
- sample 3:
- time = 30000
- flags = 1
- data = length 85, hash 9180DC2F
- sample 4:
- time = 40000
- flags = 1
- data = length 85, hash 6AE958C
- sample 5:
- time = 50000
- flags = 1
- data = length 86, hash C1C5AE60
- sample 6:
- time = 60000
- flags = 1
- data = length 87, hash B9BD2620
- sample 7:
- time = 70000
- flags = 1
- data = length 86, hash 5E69E6F9
- sample 8:
- time = 80000
- flags = 1
- data = length 90, hash C44C7DD9
- sample 9:
- time = 90000
- flags = 1
- data = length 86, hash C3FCDC6F
- sample 10:
- time = 100000
- flags = 1
- data = length 86, hash 44EA03BA
- sample 11:
- time = 110000
- flags = 1
- data = length 160, hash 9F4E1AE8
- sample 12:
- time = 120000
- flags = 1
- data = length 89, hash 31234460
- sample 13:
- time = 130000
- flags = 1
- data = length 91, hash 45012D79
- sample 14:
- time = 140000
- flags = 1
- data = length 90, hash B3E3AC75
- sample 15:
- time = 150000
- flags = 1
- data = length 87, hash B83B756B
- sample 16:
- time = 160000
- flags = 1
- data = length 86, hash 383921EB
- sample 17:
- time = 170000
- flags = 1
- data = length 97, hash 959AD270
- sample 18:
- time = 180000
- flags = 1
- data = length 92, hash 46C74FA8
- sample 19:
- time = 190000
- flags = 1
- data = length 91, hash CEA401DD
- sample 20:
- time = 200000
- flags = 1
- data = length 89, hash 48C41853
- sample 21:
- time = 210000
- flags = 1
- data = length 90, hash F23245BD
- sample 22:
- time = 220000
- flags = 1
- data = length 96, hash 95E8985D
- sample 23:
- time = 230000
- flags = 1
- data = length 96, hash 34295492
- sample 24:
- time = 240000
- flags = 1
- data = length 94, hash 4E3C9C0F
- sample 25:
- time = 250000
- flags = 1
- data = length 89, hash 28B74A29
- sample 26:
- time = 260000
- flags = 1
- data = length 87, hash BAC119A7
- sample 27:
- time = 270000
- flags = 1
- data = length 88, hash 7139FF38
- sample 28:
- time = 280000
- flags = 1
- data = length 85, hash 246E1D2A
- sample 29:
- time = 290000
- flags = 1
- data = length 86, hash 488A0900
- sample 30:
- time = 300000
- flags = 1
- data = length 90, hash 16FD17B1
- sample 31:
- time = 310000
- flags = 1
- data = length 87, hash 20E849D9
- sample 32:
- time = 320000
- flags = 1
- data = length 86, hash 23A0E9BA
- sample 33:
- time = 330000
- flags = 1
- data = length 87, hash EC935537
- sample 34:
- time = 340000
- flags = 1
- data = length 92, hash 4D9935AD
- sample 35:
- time = 350000
- flags = 1
- data = length 87, hash DEDE3FA
- sample 36:
- time = 360000
- flags = 1
- data = length 87, hash ADC25A6E
- sample 37:
- time = 370000
- flags = 1
- data = length 88, hash A1C828C5
- sample 38:
- time = 380000
- flags = 1
- data = length 89, hash 735C087A
- sample 39:
- time = 390000
- flags = 1
- data = length 89, hash 19AF5D10
- sample 40:
- time = 400000
- flags = 1
- data = length 90, hash BCCEA2BB
- sample 41:
- time = 410000
- flags = 1
- data = length 86, hash A7C934A0
- sample 42:
- time = 420000
- flags = 1
- data = length 86, hash 28BBC0A8
- sample 43:
- time = 430000
- flags = 1
- data = length 85, hash E60BB12D
- sample 44:
- time = 440000
- flags = 1
- data = length 141, hash 1D2B8920
- sample 45:
- time = 450000
- flags = 1
- data = length 121, hash 8AA3E19C
- sample 46:
- time = 460000
- flags = 1
- data = length 86, hash 24DF0F37
- sample 47:
- time = 470000
- flags = 1
- data = length 86, hash 1D1775FF
- sample 48:
- time = 480000
- flags = 1
- data = length 87, hash 5230399E
- sample 49:
- time = 490000
- flags = 1
- data = length 91, hash 6CD98305
- sample 50:
- time = 500000
- flags = 1
- data = length 88, hash 4069FBB
- sample 51:
- time = 510000
- flags = 1
- data = length 89, hash 76824ABF
- sample 52:
- time = 520000
- flags = 1
- data = length 87, hash BC1B1322
- sample 53:
- time = 530000
- flags = 1
- data = length 102, hash E01BA053
- sample 54:
- time = 540000
- flags = 1
- data = length 85, hash C09B626D
- sample 55:
- time = 550000
- flags = 1
- data = length 88, hash 6B7B404A
- sample 56:
- time = 560000
- flags = 1
- data = length 85, hash 74A15DC7
- sample 57:
- time = 570000
- flags = 1
- data = length 88, hash 38DB82E5
- sample 58:
- time = 580000
- flags = 1
- data = length 86, hash 1A39C081
- sample 59:
- time = 590000
- flags = 1
- data = length 87, hash 39FEC92
- sample 60:
- time = 600000
- flags = 1
- data = length 92, hash 278EA09
- sample 61:
- time = 610000
- flags = 1
- data = length 87, hash 28265F2D
- sample 62:
- time = 620000
- flags = 1
- data = length 86, hash CC2040C6
- sample 63:
- time = 630000
- flags = 1
- data = length 138, hash 9E07BC1F
- sample 64:
- time = 640000
- flags = 1
- data = length 85, hash 4F299670
- sample 65:
- time = 650000
- flags = 1
- data = length 125, hash B61123C3
- sample 66:
- time = 660000
- flags = 1
- data = length 89, hash 5CC688ED
- sample 67:
- time = 670000
- flags = 1
- data = length 88, hash 84AF64A6
- sample 68:
- time = 680000
- flags = 1
- data = length 89, hash A9BFC8DC
- sample 69:
- time = 690000
- flags = 1
- data = length 90, hash 2FF77060
- sample 70:
- time = 700000
- flags = 1
- data = length 96, hash E11AFD61
- sample 71:
- time = 710000
- flags = 1
- data = length 87, hash 85D14EDA
- sample 72:
- time = 720000
- flags = 1
- data = length 88, hash 5FC71D53
- sample 73:
- time = 730000
- flags = 1
- data = length 89, hash 957187B6
- sample 74:
- time = 740000
- flags = 1
- data = length 89, hash 5A776082
- sample 75:
- time = 750000
- flags = 1
- data = length 87, hash E8A83AFE
- sample 76:
- time = 760000
- flags = 1
- data = length 87, hash F1989133
- sample 77:
- time = 770000
- flags = 1
- data = length 87, hash FA06BCCA
- sample 78:
- time = 780000
- flags = 1
- data = length 86, hash BD97E0C0
- sample 79:
- time = 790000
- flags = 1
- data = length 88, hash E6AE022C
- sample 80:
- time = 800000
- flags = 1
- data = length 87, hash FB6C6169
- sample 81:
- time = 810000
- flags = 1
- data = length 87, hash DFFCD2CF
- sample 82:
- time = 820000
- flags = 1
- data = length 88, hash A4B5EB52
- sample 83:
- time = 830000
- flags = 1
- data = length 85, hash A63CF4EA
- sample 84:
- time = 840000
- flags = 1
- data = length 86, hash F126E7C7
- sample 85:
- time = 850000
- flags = 1
- data = length 86, hash 21A8B22F
- sample 86:
- time = 860000
- flags = 1
- data = length 87, hash 6520E7C1
- sample 87:
- time = 870000
- flags = 1
- data = length 88, hash 825B7423
- sample 88:
- time = 880000
- flags = 1
- data = length 88, hash DF3DBD48
- sample 89:
- time = 890000
- flags = 1
- data = length 87, hash B32C68D0
- sample 90:
- time = 900000
- flags = 1
- data = length 89, hash B99DFFCA
- sample 91:
- time = 910000
- flags = 1
- data = length 88, hash 9C8D5178
- sample 92:
- time = 920000
- flags = 1
- data = length 88, hash 48A0B19A
- sample 93:
- time = 930000
- flags = 1
- data = length 88, hash B62C94DD
- sample 94:
- time = 940000
- flags = 1
- data = length 92, hash 96DBDD46
- sample 95:
- time = 950000
- flags = 1
- data = length 87, hash 7B80E6F
- sample 96:
- time = 960000
- flags = 1
- data = length 86, hash 9C60225B
- sample 97:
- time = 970000
- flags = 1
- data = length 87, hash 45F7E6E8
- sample 98:
- time = 980000
- flags = 1
- data = length 87, hash DDC2D592
- sample 99:
- time = 990000
- flags = 1
- data = length 91, hash 173D3B26
- sample 100:
- time = 1000000
- flags = 1
- data = length 87, hash CF3629DF
- sample 101:
- time = 1010000
- flags = 1
- data = length 87, hash BBE2E7B3
- sample 102:
- time = 1020000
- flags = 1
- data = length 89, hash 89AFFB10
- sample 103:
- time = 1030000
- flags = 1
- data = length 88, hash 510DCC90
- sample 104:
- time = 1040000
- flags = 1
- data = length 88, hash CBA56E5F
- sample 105:
- time = 1050000
- flags = 1
- data = length 87, hash B4B1B3FF
- sample 106:
- time = 1060000
- flags = 1
- data = length 89, hash B976A537
- sample 107:
- time = 1070000
- flags = 1
- data = length 96, hash 43ECF2C1
- sample 108:
- time = 1080000
- flags = 1
- data = length 90, hash BB7ECB44
- sample 109:
- time = 1090000
- flags = 1
- data = length 89, hash B8E221A5
- sample 110:
- time = 1100000
- flags = 1
- data = length 86, hash B35BEF5B
- sample 111:
- time = 1110000
- flags = 1
- data = length 89, hash 9002F0EC
- sample 112:
- time = 1120000
- flags = 1
- data = length 85, hash F694B20
- sample 113:
- time = 1130000
- flags = 1
- data = length 87, hash D7CC386E
- sample 114:
- time = 1140000
- flags = 1
- data = length 89, hash EE9E0E79
- sample 115:
- time = 1150000
- flags = 1
- data = length 90, hash CA72C96B
- sample 116:
- time = 1160000
- flags = 1
- data = length 112, hash 4AD3D1B1
- sample 117:
- time = 1170000
- flags = 1
- data = length 87, hash FA568FAB
- sample 118:
- time = 1180000
- flags = 1
- data = length 90, hash 6E6948D2
- sample 119:
- time = 1190000
- flags = 1
- data = length 89, hash 5465A762
- sample 120:
- time = 1200000
- flags = 1
- data = length 87, hash BED19B40
- sample 121:
- time = 1210000
- flags = 1
- data = length 89, hash 5D05836A
- sample 122:
- time = 1220000
- flags = 1
- data = length 87, hash A8A3EF5A
- sample 123:
- time = 1230000
- flags = 1
- data = length 90, hash 6425A77A
- sample 124:
- time = 1240000
- flags = 1
- data = length 92, hash 7F320FA
- sample 125:
- time = 1250000
- flags = 1
- data = length 89, hash 2C7837D6
- sample 126:
- time = 1260000
- flags = 1
- data = length 86, hash 58D56685
- sample 127:
- time = 1270000
- flags = 1
- data = length 91, hash ADC5072F
- sample 128:
- time = 1280000
- flags = 1
- data = length 85, hash 53EFD93
- sample 129:
- time = 1290000
- flags = 1
- data = length 87, hash D006B535
- sample 130:
- time = 1300000
- flags = 1
- data = length 86, hash AE944625
- sample 131:
- time = 1310000
- flags = 1
- data = length 89, hash B5D3C81D
- sample 132:
- time = 1320000
- flags = 1
- data = length 86, hash 3BB1D0E7
- sample 133:
- time = 1330000
- flags = 1
- data = length 102, hash 16EEC441
- sample 134:
- time = 1340000
- flags = 1
- data = length 90, hash 1005B936
- sample 135:
- time = 1350000
- flags = 1
- data = length 85, hash 15EEBF9A
- sample 136:
- time = 1360000
- flags = 1
- data = length 87, hash 37C83AC2
- sample 137:
- time = 1370000
- flags = 1
- data = length 85, hash 2D27855D
- sample 138:
- time = 1380000
- flags = 1
- data = length 85, hash 753EB7C6
- sample 139:
- time = 1390000
- flags = 1
- data = length 91, hash C0813318
- sample 140:
- time = 1400000
- flags = 1
- data = length 89, hash 3A6468AC
- sample 141:
- time = 1410000
- flags = 1
- data = length 88, hash 3D220ABC
- sample 142:
- time = 1420000
- flags = 1
- data = length 140, hash 7949ABC7
- sample 143:
- time = 1430000
- flags = 1
- data = length 92, hash F19AFA45
- sample 144:
- time = 1440000
- flags = 1
- data = length 90, hash 3D21587C
- sample 145:
- time = 1450000
- flags = 1
- data = length 89, hash 5C12226C
- sample 146:
- time = 1460000
- flags = 1
- data = length 90, hash 22BA14FC
- sample 147:
- time = 1470000
- flags = 1
- data = length 88, hash F064B21C
- sample 148:
- time = 1480000
- flags = 1
- data = length 87, hash 6D7906B9
- sample 149:
- time = 1490000
- flags = 1
- data = length 88, hash 6756A484
- sample 150:
- time = 1500000
- flags = 1
- data = length 91, hash C95C00B6
- sample 151:
- time = 1510000
- flags = 1
- data = length 87, hash 728D8119
- sample 152:
- time = 1520000
- flags = 1
- data = length 90, hash C43DA1B4
- sample 153:
- time = 1530000
- flags = 1
- data = length 88, hash C181BB57
- sample 154:
- time = 1540000
- flags = 1
- data = length 84, hash F75B1639
- sample 155:
- time = 1550000
- flags = 1
- data = length 87, hash B6F32978
- sample 156:
- time = 1560000
- flags = 1
- data = length 90, hash 36D6E2D7
- sample 157:
- time = 1570000
- flags = 1
- data = length 87, hash 4C9657A7
- sample 158:
- time = 1580000
- flags = 1
- data = length 89, hash C3BDB9B7
- sample 159:
- time = 1590000
- flags = 1
- data = length 88, hash DB51087E
- sample 160:
- time = 1600000
- flags = 1
- data = length 86, hash 1550F998
- sample 161:
- time = 1610000
- flags = 1
- data = length 86, hash A445FAD4
- sample 162:
- time = 1620000
- flags = 1
- data = length 85, hash 60D3362C
- sample 163:
- time = 1630000
- flags = 1
- data = length 172, hash 945D63E4
- sample 164:
- time = 1640000
- flags = 1
- data = length 107, hash 585B7C04
- sample 165:
- time = 1650000
- flags = 1
- data = length 110, hash 74BECF69
- sample 166:
- time = 1660000
- flags = 1
- data = length 87, hash 63DE1D24
- sample 167:
- time = 1670000
- flags = 1
- data = length 90, hash 1C1D28DB
- sample 168:
- time = 1680000
- flags = 1
- data = length 87, hash CB382A67
- sample 169:
- time = 1690000
- flags = 1
- data = length 85, hash A227BA13
- sample 170:
- time = 1700000
- flags = 1
- data = length 86, hash EFD8B10B
- sample 171:
- time = 1710000
- flags = 1
- data = length 87, hash 47FF364A
- sample 172:
- time = 1720000
- flags = 1
- data = length 91, hash 31D4B48A
- sample 173:
- time = 1730000
- flags = 1
- data = length 91, hash DD69BD85
- sample 174:
- time = 1740000
- flags = 1
- data = length 88, hash AF1A95C6
- sample 175:
- time = 1750000
- flags = 1
- data = length 87, hash 2FB8AF74
- sample 176:
- time = 1760000
- flags = 1
- data = length 92, hash 173C707A
- sample 177:
- time = 1770000
- flags = 1
- data = length 88, hash 5F58F5E8
- sample 178:
- time = 1780000
- flags = 1
- data = length 91, hash D449785F
- sample 179:
- time = 1790000
- flags = 1
- data = length 91, hash CE2CB465
- sample 180:
- time = 1800000
- flags = 1
- data = length 93, hash ABC1C62E
- sample 181:
- time = 1810000
- flags = 1
- data = length 87, hash 83B4B9A0
- sample 182:
- time = 1820000
- flags = 1
- data = length 87, hash 3220D562
- sample 183:
- time = 1830000
- flags = 1
- data = length 86, hash 64D21AA1
- sample 184:
- time = 1840000
- flags = 1
- data = length 86, hash A1FAAF2C
- sample 185:
- time = 1850000
- flags = 1
- data = length 86, hash ECA80F7E
- sample 186:
- time = 1860000
- flags = 1
- data = length 86, hash FEB03B2C
- sample 187:
- time = 1870000
- flags = 1
- data = length 85, hash 2C2E6B2F
- sample 188:
- time = 1880000
- flags = 1
- data = length 89, hash A0D7AC3
- sample 189:
- time = 1890000
- flags = 1
- data = length 87, hash 83739547
- sample 190:
- time = 1900000
- flags = 1
- data = length 86, hash 991E531E
- sample 191:
- time = 1910000
- flags = 1
- data = length 88, hash 16B287A3
- sample 192:
- time = 1920000
- flags = 1
- data = length 86, hash FC86EED
- sample 193:
- time = 1930000
- flags = 1
- data = length 86, hash 96AF0248
- sample 194:
- time = 1940000
- flags = 1
- data = length 86, hash 288402C8
- sample 195:
- time = 1950000
- flags = 1
- data = length 87, hash 4BBA7912
- sample 196:
- time = 1960000
- flags = 1
- data = length 86, hash 4A59C719
- sample 197:
- time = 1970000
- flags = 1
- data = length 85, hash 906E8187
- sample 198:
- time = 1980000
- flags = 1
- data = length 90, hash CB8B755D
- sample 199:
- time = 1990000
- flags = 1
- data = length 87, hash C8E02C
- sample 200:
- time = 2000000
- flags = 1
- data = length 88, hash ACF4D89A
- sample 201:
- time = 2010000
- flags = 1
- data = length 86, hash 510FE048
- sample 202:
- time = 2020000
- flags = 1
- data = length 86, hash 64983E46
- sample 203:
- time = 2030000
- flags = 1
- data = length 86, hash CEA76A1E
- sample 204:
- time = 2040000
- flags = 1
- data = length 87, hash AADE498E
- sample 205:
- time = 2050000
- flags = 1
- data = length 127, hash 353A6D8C
- sample 206:
- time = 2060000
- flags = 1
- data = length 87, hash 29E18E62
- sample 207:
- time = 2070000
- flags = 1
- data = length 87, hash 2CF7B30F
- sample 208:
- time = 2080000
- flags = 1
- data = length 94, hash 758704C3
- sample 209:
- time = 2090000
- flags = 1
- data = length 88, hash C2153A4C
- sample 210:
- time = 2100000
- flags = 1
- data = length 86, hash A0A83DA5
- sample 211:
- time = 2110000
- flags = 1
- data = length 86, hash 41017D7F
- sample 212:
- time = 2120000
- flags = 1
- data = length 93, hash 686B0CA2
- sample 213:
- time = 2130000
- flags = 1
- data = length 86, hash 554D16CC
- sample 214:
- time = 2140000
- flags = 1
- data = length 88, hash 99D72771
- sample 215:
- time = 2150000
- flags = 1
- data = length 88, hash 7176DFBF
- sample 216:
- time = 2160000
- flags = 1
- data = length 86, hash BAA22669
- sample 217:
- time = 2170000
- flags = 1
- data = length 88, hash B00B0D3C
- sample 218:
- time = 2180000
- flags = 1
- data = length 89, hash 73FED83A
- sample 219:
- time = 2190000
- flags = 1
- data = length 86, hash 4A4138D3
- sample 220:
- time = 2200000
- flags = 1
- data = length 89, hash E0A860FF
- sample 221:
- time = 2210000
- flags = 1
- data = length 95, hash EE5A8AED
- sample 222:
- time = 2220000
- flags = 1
- data = length 92, hash 36DBD7FD
- sample 223:
- time = 2230000
- flags = 1
- data = length 88, hash EE47A7E4
- sample 224:
- time = 2240000
- flags = 1
- data = length 100, hash 2E1A603F
- sample 225:
- time = 2250000
- flags = 1
- data = length 89, hash 657ED4A3
- sample 226:
- time = 2260000
- flags = 1
- data = length 86, hash A833DC7B
- sample 227:
- time = 2270000
- flags = 1
- data = length 88, hash 81E80732
- sample 228:
- time = 2280000
- flags = 1
- data = length 91, hash FA256A0F
- sample 229:
- time = 2290000
- flags = 1
- data = length 88, hash A63A4DBA
- sample 230:
- time = 2300000
- flags = 1
- data = length 88, hash 67910A9F
- sample 231:
- time = 2310000
- flags = 1
- data = length 86, hash EB387DB6
- sample 232:
- time = 2320000
- flags = 1
- data = length 88, hash 5ACAAC2A
- sample 233:
- time = 2330000
- flags = 1
- data = length 86, hash 6ADF2E1F
- sample 234:
- time = 2340000
- flags = 1
- data = length 85, hash 9D064471
- sample 235:
- time = 2350000
- flags = 1
- data = length 87, hash F176C59
- sample 236:
- time = 2360000
- flags = 1
- data = length 89, hash 5CA40CE4
- sample 237:
- time = 2370000
- flags = 1
- data = length 88, hash 67B944FC
- sample 238:
- time = 2380000
- flags = 1
- data = length 86, hash B3A84EC8
- sample 239:
- time = 2390000
- flags = 1
- data = length 92, hash A6ACF94B
- sample 240:
- time = 2400000
- flags = 1
- data = length 88, hash CB0C9730
- sample 241:
- time = 2410000
- flags = 1
- data = length 88, hash C79FE804
- sample 242:
- time = 2420000
- flags = 1
- data = length 88, hash A74C7F0A
- sample 243:
- time = 2430000
- flags = 1
- data = length 91, hash 55F6F0A5
- sample 244:
- time = 2440000
- flags = 1
- data = length 93, hash 330F33E7
- sample 245:
- time = 2450000
- flags = 1
- data = length 89, hash 614AFBA0
- sample 246:
- time = 2460000
- flags = 1
- data = length 87, hash 3CE4652D
- sample 247:
- time = 2470000
- flags = 1
- data = length 87, hash 4EFD5467
- sample 248:
- time = 2480000
- flags = 1
- data = length 86, hash D81B3EB8
- sample 249:
- time = 2490000
- flags = 1
- data = length 88, hash 96CB6871
- sample 250:
- time = 2500000
- flags = 1
- data = length 88, hash E9DF2786
- sample 251:
- time = 2510000
- flags = 1
- data = length 89, hash 2CA33D96
- sample 252:
- time = 2520000
- flags = 1
- data = length 90, hash 96BDE594
- sample 253:
- time = 2530000
- flags = 1
- data = length 87, hash C261493C
- sample 254:
- time = 2540000
- flags = 1
- data = length 86, hash D037318E
- sample 255:
- time = 2550000
- flags = 1
- data = length 88, hash BC15BC88
- sample 256:
- time = 2560000
- flags = 1
- data = length 91, hash A8361A51
- sample 257:
- time = 2570000
- flags = 1
- data = length 87, hash 4AFDB5F2
- sample 258:
- time = 2580000
- flags = 1
- data = length 87, hash 6447F8CB
- sample 259:
- time = 2590000
- flags = 1
- data = length 89, hash 48305229
- sample 260:
- time = 2600000
- flags = 1
- data = length 87, hash 8741D9E7
- sample 261:
- time = 2610000
- flags = 1
- data = length 120, hash 761F020C
- sample 262:
- time = 2620000
- flags = 1
- data = length 139, hash AECE2E57
- sample 263:
- time = 2630000
- flags = 1
- data = length 166, hash 6288797A
- sample 264:
- time = 2640000
- flags = 1
- data = length 144, hash 437821A0
- sample 265:
- time = 2650000
- flags = 1
- data = length 113, hash FCCBEDF1
- sample 266:
- time = 2660000
- flags = 1
- data = length 108, hash C4040614
- sample 267:
- time = 2670000
- flags = 1
- data = length 125, hash E29064C2
- sample 268:
- time = 2680000
- flags = 1
- data = length 126, hash D42D24FF
- sample 269:
- time = 2690000
- flags = 1
- data = length 122, hash 30AF267D
- sample 270:
- time = 2700000
- flags = 1
- data = length 122, hash 45CEC1FB
- sample 271:
- time = 2710000
- flags = 1
- data = length 134, hash 59143FE2
- sample 272:
- time = 2720000
- flags = 1
- data = length 134, hash BD52A84
- sample 273:
- time = 2730000
- flags = 1
- data = length 120, hash 745C3714
- sample 274:
- time = 2740000
- flags = 1
- data = length 126, hash 505E117B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus.1.dump b/tree/library/extractor/src/test/assets/ogg/bear.opus.1.dump
deleted file mode 100644
index ffe458f..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear.opus.1.dump
+++ /dev/null
@@ -1,770 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2747500
- getPosition(0) = [[timeUs=0, position=125]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/opus
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 19, hash BFE794DB
- data = length 8, hash CA22068C
- data = length 8, hash 79C07075
- total output bytes = 17031
- sample count = 184
- sample 0:
- time = 910000
- flags = 1
- data = length 88, hash 9C8D5178
- sample 1:
- time = 920000
- flags = 1
- data = length 88, hash 48A0B19A
- sample 2:
- time = 930000
- flags = 1
- data = length 88, hash B62C94DD
- sample 3:
- time = 940000
- flags = 1
- data = length 92, hash 96DBDD46
- sample 4:
- time = 950000
- flags = 1
- data = length 87, hash 7B80E6F
- sample 5:
- time = 960000
- flags = 1
- data = length 86, hash 9C60225B
- sample 6:
- time = 970000
- flags = 1
- data = length 87, hash 45F7E6E8
- sample 7:
- time = 980000
- flags = 1
- data = length 87, hash DDC2D592
- sample 8:
- time = 990000
- flags = 1
- data = length 91, hash 173D3B26
- sample 9:
- time = 1000000
- flags = 1
- data = length 87, hash CF3629DF
- sample 10:
- time = 1010000
- flags = 1
- data = length 87, hash BBE2E7B3
- sample 11:
- time = 1020000
- flags = 1
- data = length 89, hash 89AFFB10
- sample 12:
- time = 1030000
- flags = 1
- data = length 88, hash 510DCC90
- sample 13:
- time = 1040000
- flags = 1
- data = length 88, hash CBA56E5F
- sample 14:
- time = 1050000
- flags = 1
- data = length 87, hash B4B1B3FF
- sample 15:
- time = 1060000
- flags = 1
- data = length 89, hash B976A537
- sample 16:
- time = 1070000
- flags = 1
- data = length 96, hash 43ECF2C1
- sample 17:
- time = 1080000
- flags = 1
- data = length 90, hash BB7ECB44
- sample 18:
- time = 1090000
- flags = 1
- data = length 89, hash B8E221A5
- sample 19:
- time = 1100000
- flags = 1
- data = length 86, hash B35BEF5B
- sample 20:
- time = 1110000
- flags = 1
- data = length 89, hash 9002F0EC
- sample 21:
- time = 1120000
- flags = 1
- data = length 85, hash F694B20
- sample 22:
- time = 1130000
- flags = 1
- data = length 87, hash D7CC386E
- sample 23:
- time = 1140000
- flags = 1
- data = length 89, hash EE9E0E79
- sample 24:
- time = 1150000
- flags = 1
- data = length 90, hash CA72C96B
- sample 25:
- time = 1160000
- flags = 1
- data = length 112, hash 4AD3D1B1
- sample 26:
- time = 1170000
- flags = 1
- data = length 87, hash FA568FAB
- sample 27:
- time = 1180000
- flags = 1
- data = length 90, hash 6E6948D2
- sample 28:
- time = 1190000
- flags = 1
- data = length 89, hash 5465A762
- sample 29:
- time = 1200000
- flags = 1
- data = length 87, hash BED19B40
- sample 30:
- time = 1210000
- flags = 1
- data = length 89, hash 5D05836A
- sample 31:
- time = 1220000
- flags = 1
- data = length 87, hash A8A3EF5A
- sample 32:
- time = 1230000
- flags = 1
- data = length 90, hash 6425A77A
- sample 33:
- time = 1240000
- flags = 1
- data = length 92, hash 7F320FA
- sample 34:
- time = 1250000
- flags = 1
- data = length 89, hash 2C7837D6
- sample 35:
- time = 1260000
- flags = 1
- data = length 86, hash 58D56685
- sample 36:
- time = 1270000
- flags = 1
- data = length 91, hash ADC5072F
- sample 37:
- time = 1280000
- flags = 1
- data = length 85, hash 53EFD93
- sample 38:
- time = 1290000
- flags = 1
- data = length 87, hash D006B535
- sample 39:
- time = 1300000
- flags = 1
- data = length 86, hash AE944625
- sample 40:
- time = 1310000
- flags = 1
- data = length 89, hash B5D3C81D
- sample 41:
- time = 1320000
- flags = 1
- data = length 86, hash 3BB1D0E7
- sample 42:
- time = 1330000
- flags = 1
- data = length 102, hash 16EEC441
- sample 43:
- time = 1340000
- flags = 1
- data = length 90, hash 1005B936
- sample 44:
- time = 1350000
- flags = 1
- data = length 85, hash 15EEBF9A
- sample 45:
- time = 1360000
- flags = 1
- data = length 87, hash 37C83AC2
- sample 46:
- time = 1370000
- flags = 1
- data = length 85, hash 2D27855D
- sample 47:
- time = 1380000
- flags = 1
- data = length 85, hash 753EB7C6
- sample 48:
- time = 1390000
- flags = 1
- data = length 91, hash C0813318
- sample 49:
- time = 1400000
- flags = 1
- data = length 89, hash 3A6468AC
- sample 50:
- time = 1410000
- flags = 1
- data = length 88, hash 3D220ABC
- sample 51:
- time = 1420000
- flags = 1
- data = length 140, hash 7949ABC7
- sample 52:
- time = 1430000
- flags = 1
- data = length 92, hash F19AFA45
- sample 53:
- time = 1440000
- flags = 1
- data = length 90, hash 3D21587C
- sample 54:
- time = 1450000
- flags = 1
- data = length 89, hash 5C12226C
- sample 55:
- time = 1460000
- flags = 1
- data = length 90, hash 22BA14FC
- sample 56:
- time = 1470000
- flags = 1
- data = length 88, hash F064B21C
- sample 57:
- time = 1480000
- flags = 1
- data = length 87, hash 6D7906B9
- sample 58:
- time = 1490000
- flags = 1
- data = length 88, hash 6756A484
- sample 59:
- time = 1500000
- flags = 1
- data = length 91, hash C95C00B6
- sample 60:
- time = 1510000
- flags = 1
- data = length 87, hash 728D8119
- sample 61:
- time = 1520000
- flags = 1
- data = length 90, hash C43DA1B4
- sample 62:
- time = 1530000
- flags = 1
- data = length 88, hash C181BB57
- sample 63:
- time = 1540000
- flags = 1
- data = length 84, hash F75B1639
- sample 64:
- time = 1550000
- flags = 1
- data = length 87, hash B6F32978
- sample 65:
- time = 1560000
- flags = 1
- data = length 90, hash 36D6E2D7
- sample 66:
- time = 1570000
- flags = 1
- data = length 87, hash 4C9657A7
- sample 67:
- time = 1580000
- flags = 1
- data = length 89, hash C3BDB9B7
- sample 68:
- time = 1590000
- flags = 1
- data = length 88, hash DB51087E
- sample 69:
- time = 1600000
- flags = 1
- data = length 86, hash 1550F998
- sample 70:
- time = 1610000
- flags = 1
- data = length 86, hash A445FAD4
- sample 71:
- time = 1620000
- flags = 1
- data = length 85, hash 60D3362C
- sample 72:
- time = 1630000
- flags = 1
- data = length 172, hash 945D63E4
- sample 73:
- time = 1640000
- flags = 1
- data = length 107, hash 585B7C04
- sample 74:
- time = 1650000
- flags = 1
- data = length 110, hash 74BECF69
- sample 75:
- time = 1660000
- flags = 1
- data = length 87, hash 63DE1D24
- sample 76:
- time = 1670000
- flags = 1
- data = length 90, hash 1C1D28DB
- sample 77:
- time = 1680000
- flags = 1
- data = length 87, hash CB382A67
- sample 78:
- time = 1690000
- flags = 1
- data = length 85, hash A227BA13
- sample 79:
- time = 1700000
- flags = 1
- data = length 86, hash EFD8B10B
- sample 80:
- time = 1710000
- flags = 1
- data = length 87, hash 47FF364A
- sample 81:
- time = 1720000
- flags = 1
- data = length 91, hash 31D4B48A
- sample 82:
- time = 1730000
- flags = 1
- data = length 91, hash DD69BD85
- sample 83:
- time = 1740000
- flags = 1
- data = length 88, hash AF1A95C6
- sample 84:
- time = 1750000
- flags = 1
- data = length 87, hash 2FB8AF74
- sample 85:
- time = 1760000
- flags = 1
- data = length 92, hash 173C707A
- sample 86:
- time = 1770000
- flags = 1
- data = length 88, hash 5F58F5E8
- sample 87:
- time = 1780000
- flags = 1
- data = length 91, hash D449785F
- sample 88:
- time = 1790000
- flags = 1
- data = length 91, hash CE2CB465
- sample 89:
- time = 1800000
- flags = 1
- data = length 93, hash ABC1C62E
- sample 90:
- time = 1810000
- flags = 1
- data = length 87, hash 83B4B9A0
- sample 91:
- time = 1820000
- flags = 1
- data = length 87, hash 3220D562
- sample 92:
- time = 1830000
- flags = 1
- data = length 86, hash 64D21AA1
- sample 93:
- time = 1840000
- flags = 1
- data = length 86, hash A1FAAF2C
- sample 94:
- time = 1850000
- flags = 1
- data = length 86, hash ECA80F7E
- sample 95:
- time = 1860000
- flags = 1
- data = length 86, hash FEB03B2C
- sample 96:
- time = 1870000
- flags = 1
- data = length 85, hash 2C2E6B2F
- sample 97:
- time = 1880000
- flags = 1
- data = length 89, hash A0D7AC3
- sample 98:
- time = 1890000
- flags = 1
- data = length 87, hash 83739547
- sample 99:
- time = 1900000
- flags = 1
- data = length 86, hash 991E531E
- sample 100:
- time = 1910000
- flags = 1
- data = length 88, hash 16B287A3
- sample 101:
- time = 1920000
- flags = 1
- data = length 86, hash FC86EED
- sample 102:
- time = 1930000
- flags = 1
- data = length 86, hash 96AF0248
- sample 103:
- time = 1940000
- flags = 1
- data = length 86, hash 288402C8
- sample 104:
- time = 1950000
- flags = 1
- data = length 87, hash 4BBA7912
- sample 105:
- time = 1960000
- flags = 1
- data = length 86, hash 4A59C719
- sample 106:
- time = 1970000
- flags = 1
- data = length 85, hash 906E8187
- sample 107:
- time = 1980000
- flags = 1
- data = length 90, hash CB8B755D
- sample 108:
- time = 1990000
- flags = 1
- data = length 87, hash C8E02C
- sample 109:
- time = 2000000
- flags = 1
- data = length 88, hash ACF4D89A
- sample 110:
- time = 2010000
- flags = 1
- data = length 86, hash 510FE048
- sample 111:
- time = 2020000
- flags = 1
- data = length 86, hash 64983E46
- sample 112:
- time = 2030000
- flags = 1
- data = length 86, hash CEA76A1E
- sample 113:
- time = 2040000
- flags = 1
- data = length 87, hash AADE498E
- sample 114:
- time = 2050000
- flags = 1
- data = length 127, hash 353A6D8C
- sample 115:
- time = 2060000
- flags = 1
- data = length 87, hash 29E18E62
- sample 116:
- time = 2070000
- flags = 1
- data = length 87, hash 2CF7B30F
- sample 117:
- time = 2080000
- flags = 1
- data = length 94, hash 758704C3
- sample 118:
- time = 2090000
- flags = 1
- data = length 88, hash C2153A4C
- sample 119:
- time = 2100000
- flags = 1
- data = length 86, hash A0A83DA5
- sample 120:
- time = 2110000
- flags = 1
- data = length 86, hash 41017D7F
- sample 121:
- time = 2120000
- flags = 1
- data = length 93, hash 686B0CA2
- sample 122:
- time = 2130000
- flags = 1
- data = length 86, hash 554D16CC
- sample 123:
- time = 2140000
- flags = 1
- data = length 88, hash 99D72771
- sample 124:
- time = 2150000
- flags = 1
- data = length 88, hash 7176DFBF
- sample 125:
- time = 2160000
- flags = 1
- data = length 86, hash BAA22669
- sample 126:
- time = 2170000
- flags = 1
- data = length 88, hash B00B0D3C
- sample 127:
- time = 2180000
- flags = 1
- data = length 89, hash 73FED83A
- sample 128:
- time = 2190000
- flags = 1
- data = length 86, hash 4A4138D3
- sample 129:
- time = 2200000
- flags = 1
- data = length 89, hash E0A860FF
- sample 130:
- time = 2210000
- flags = 1
- data = length 95, hash EE5A8AED
- sample 131:
- time = 2220000
- flags = 1
- data = length 92, hash 36DBD7FD
- sample 132:
- time = 2230000
- flags = 1
- data = length 88, hash EE47A7E4
- sample 133:
- time = 2240000
- flags = 1
- data = length 100, hash 2E1A603F
- sample 134:
- time = 2250000
- flags = 1
- data = length 89, hash 657ED4A3
- sample 135:
- time = 2260000
- flags = 1
- data = length 86, hash A833DC7B
- sample 136:
- time = 2270000
- flags = 1
- data = length 88, hash 81E80732
- sample 137:
- time = 2280000
- flags = 1
- data = length 91, hash FA256A0F
- sample 138:
- time = 2290000
- flags = 1
- data = length 88, hash A63A4DBA
- sample 139:
- time = 2300000
- flags = 1
- data = length 88, hash 67910A9F
- sample 140:
- time = 2310000
- flags = 1
- data = length 86, hash EB387DB6
- sample 141:
- time = 2320000
- flags = 1
- data = length 88, hash 5ACAAC2A
- sample 142:
- time = 2330000
- flags = 1
- data = length 86, hash 6ADF2E1F
- sample 143:
- time = 2340000
- flags = 1
- data = length 85, hash 9D064471
- sample 144:
- time = 2350000
- flags = 1
- data = length 87, hash F176C59
- sample 145:
- time = 2360000
- flags = 1
- data = length 89, hash 5CA40CE4
- sample 146:
- time = 2370000
- flags = 1
- data = length 88, hash 67B944FC
- sample 147:
- time = 2380000
- flags = 1
- data = length 86, hash B3A84EC8
- sample 148:
- time = 2390000
- flags = 1
- data = length 92, hash A6ACF94B
- sample 149:
- time = 2400000
- flags = 1
- data = length 88, hash CB0C9730
- sample 150:
- time = 2410000
- flags = 1
- data = length 88, hash C79FE804
- sample 151:
- time = 2420000
- flags = 1
- data = length 88, hash A74C7F0A
- sample 152:
- time = 2430000
- flags = 1
- data = length 91, hash 55F6F0A5
- sample 153:
- time = 2440000
- flags = 1
- data = length 93, hash 330F33E7
- sample 154:
- time = 2450000
- flags = 1
- data = length 89, hash 614AFBA0
- sample 155:
- time = 2460000
- flags = 1
- data = length 87, hash 3CE4652D
- sample 156:
- time = 2470000
- flags = 1
- data = length 87, hash 4EFD5467
- sample 157:
- time = 2480000
- flags = 1
- data = length 86, hash D81B3EB8
- sample 158:
- time = 2490000
- flags = 1
- data = length 88, hash 96CB6871
- sample 159:
- time = 2500000
- flags = 1
- data = length 88, hash E9DF2786
- sample 160:
- time = 2510000
- flags = 1
- data = length 89, hash 2CA33D96
- sample 161:
- time = 2520000
- flags = 1
- data = length 90, hash 96BDE594
- sample 162:
- time = 2530000
- flags = 1
- data = length 87, hash C261493C
- sample 163:
- time = 2540000
- flags = 1
- data = length 86, hash D037318E
- sample 164:
- time = 2550000
- flags = 1
- data = length 88, hash BC15BC88
- sample 165:
- time = 2560000
- flags = 1
- data = length 91, hash A8361A51
- sample 166:
- time = 2570000
- flags = 1
- data = length 87, hash 4AFDB5F2
- sample 167:
- time = 2580000
- flags = 1
- data = length 87, hash 6447F8CB
- sample 168:
- time = 2590000
- flags = 1
- data = length 89, hash 48305229
- sample 169:
- time = 2600000
- flags = 1
- data = length 87, hash 8741D9E7
- sample 170:
- time = 2610000
- flags = 1
- data = length 120, hash 761F020C
- sample 171:
- time = 2620000
- flags = 1
- data = length 139, hash AECE2E57
- sample 172:
- time = 2630000
- flags = 1
- data = length 166, hash 6288797A
- sample 173:
- time = 2640000
- flags = 1
- data = length 144, hash 437821A0
- sample 174:
- time = 2650000
- flags = 1
- data = length 113, hash FCCBEDF1
- sample 175:
- time = 2660000
- flags = 1
- data = length 108, hash C4040614
- sample 176:
- time = 2670000
- flags = 1
- data = length 125, hash E29064C2
- sample 177:
- time = 2680000
- flags = 1
- data = length 126, hash D42D24FF
- sample 178:
- time = 2690000
- flags = 1
- data = length 122, hash 30AF267D
- sample 179:
- time = 2700000
- flags = 1
- data = length 122, hash 45CEC1FB
- sample 180:
- time = 2710000
- flags = 1
- data = length 134, hash 59143FE2
- sample 181:
- time = 2720000
- flags = 1
- data = length 134, hash BD52A84
- sample 182:
- time = 2730000
- flags = 1
- data = length 120, hash 745C3714
- sample 183:
- time = 2740000
- flags = 1
- data = length 126, hash 505E117B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus.2.dump b/tree/library/extractor/src/test/assets/ogg/bear.opus.2.dump
deleted file mode 100644
index 9235aff..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear.opus.2.dump
+++ /dev/null
@@ -1,402 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2747500
- getPosition(0) = [[timeUs=0, position=125]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/opus
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 19, hash BFE794DB
- data = length 8, hash CA22068C
- data = length 8, hash 79C07075
- total output bytes = 8698
- sample count = 92
- sample 0:
- time = 1830000
- flags = 1
- data = length 86, hash 64D21AA1
- sample 1:
- time = 1840000
- flags = 1
- data = length 86, hash A1FAAF2C
- sample 2:
- time = 1850000
- flags = 1
- data = length 86, hash ECA80F7E
- sample 3:
- time = 1860000
- flags = 1
- data = length 86, hash FEB03B2C
- sample 4:
- time = 1870000
- flags = 1
- data = length 85, hash 2C2E6B2F
- sample 5:
- time = 1880000
- flags = 1
- data = length 89, hash A0D7AC3
- sample 6:
- time = 1890000
- flags = 1
- data = length 87, hash 83739547
- sample 7:
- time = 1900000
- flags = 1
- data = length 86, hash 991E531E
- sample 8:
- time = 1910000
- flags = 1
- data = length 88, hash 16B287A3
- sample 9:
- time = 1920000
- flags = 1
- data = length 86, hash FC86EED
- sample 10:
- time = 1930000
- flags = 1
- data = length 86, hash 96AF0248
- sample 11:
- time = 1940000
- flags = 1
- data = length 86, hash 288402C8
- sample 12:
- time = 1950000
- flags = 1
- data = length 87, hash 4BBA7912
- sample 13:
- time = 1960000
- flags = 1
- data = length 86, hash 4A59C719
- sample 14:
- time = 1970000
- flags = 1
- data = length 85, hash 906E8187
- sample 15:
- time = 1980000
- flags = 1
- data = length 90, hash CB8B755D
- sample 16:
- time = 1990000
- flags = 1
- data = length 87, hash C8E02C
- sample 17:
- time = 2000000
- flags = 1
- data = length 88, hash ACF4D89A
- sample 18:
- time = 2010000
- flags = 1
- data = length 86, hash 510FE048
- sample 19:
- time = 2020000
- flags = 1
- data = length 86, hash 64983E46
- sample 20:
- time = 2030000
- flags = 1
- data = length 86, hash CEA76A1E
- sample 21:
- time = 2040000
- flags = 1
- data = length 87, hash AADE498E
- sample 22:
- time = 2050000
- flags = 1
- data = length 127, hash 353A6D8C
- sample 23:
- time = 2060000
- flags = 1
- data = length 87, hash 29E18E62
- sample 24:
- time = 2070000
- flags = 1
- data = length 87, hash 2CF7B30F
- sample 25:
- time = 2080000
- flags = 1
- data = length 94, hash 758704C3
- sample 26:
- time = 2090000
- flags = 1
- data = length 88, hash C2153A4C
- sample 27:
- time = 2100000
- flags = 1
- data = length 86, hash A0A83DA5
- sample 28:
- time = 2110000
- flags = 1
- data = length 86, hash 41017D7F
- sample 29:
- time = 2120000
- flags = 1
- data = length 93, hash 686B0CA2
- sample 30:
- time = 2130000
- flags = 1
- data = length 86, hash 554D16CC
- sample 31:
- time = 2140000
- flags = 1
- data = length 88, hash 99D72771
- sample 32:
- time = 2150000
- flags = 1
- data = length 88, hash 7176DFBF
- sample 33:
- time = 2160000
- flags = 1
- data = length 86, hash BAA22669
- sample 34:
- time = 2170000
- flags = 1
- data = length 88, hash B00B0D3C
- sample 35:
- time = 2180000
- flags = 1
- data = length 89, hash 73FED83A
- sample 36:
- time = 2190000
- flags = 1
- data = length 86, hash 4A4138D3
- sample 37:
- time = 2200000
- flags = 1
- data = length 89, hash E0A860FF
- sample 38:
- time = 2210000
- flags = 1
- data = length 95, hash EE5A8AED
- sample 39:
- time = 2220000
- flags = 1
- data = length 92, hash 36DBD7FD
- sample 40:
- time = 2230000
- flags = 1
- data = length 88, hash EE47A7E4
- sample 41:
- time = 2240000
- flags = 1
- data = length 100, hash 2E1A603F
- sample 42:
- time = 2250000
- flags = 1
- data = length 89, hash 657ED4A3
- sample 43:
- time = 2260000
- flags = 1
- data = length 86, hash A833DC7B
- sample 44:
- time = 2270000
- flags = 1
- data = length 88, hash 81E80732
- sample 45:
- time = 2280000
- flags = 1
- data = length 91, hash FA256A0F
- sample 46:
- time = 2290000
- flags = 1
- data = length 88, hash A63A4DBA
- sample 47:
- time = 2300000
- flags = 1
- data = length 88, hash 67910A9F
- sample 48:
- time = 2310000
- flags = 1
- data = length 86, hash EB387DB6
- sample 49:
- time = 2320000
- flags = 1
- data = length 88, hash 5ACAAC2A
- sample 50:
- time = 2330000
- flags = 1
- data = length 86, hash 6ADF2E1F
- sample 51:
- time = 2340000
- flags = 1
- data = length 85, hash 9D064471
- sample 52:
- time = 2350000
- flags = 1
- data = length 87, hash F176C59
- sample 53:
- time = 2360000
- flags = 1
- data = length 89, hash 5CA40CE4
- sample 54:
- time = 2370000
- flags = 1
- data = length 88, hash 67B944FC
- sample 55:
- time = 2380000
- flags = 1
- data = length 86, hash B3A84EC8
- sample 56:
- time = 2390000
- flags = 1
- data = length 92, hash A6ACF94B
- sample 57:
- time = 2400000
- flags = 1
- data = length 88, hash CB0C9730
- sample 58:
- time = 2410000
- flags = 1
- data = length 88, hash C79FE804
- sample 59:
- time = 2420000
- flags = 1
- data = length 88, hash A74C7F0A
- sample 60:
- time = 2430000
- flags = 1
- data = length 91, hash 55F6F0A5
- sample 61:
- time = 2440000
- flags = 1
- data = length 93, hash 330F33E7
- sample 62:
- time = 2450000
- flags = 1
- data = length 89, hash 614AFBA0
- sample 63:
- time = 2460000
- flags = 1
- data = length 87, hash 3CE4652D
- sample 64:
- time = 2470000
- flags = 1
- data = length 87, hash 4EFD5467
- sample 65:
- time = 2480000
- flags = 1
- data = length 86, hash D81B3EB8
- sample 66:
- time = 2490000
- flags = 1
- data = length 88, hash 96CB6871
- sample 67:
- time = 2500000
- flags = 1
- data = length 88, hash E9DF2786
- sample 68:
- time = 2510000
- flags = 1
- data = length 89, hash 2CA33D96
- sample 69:
- time = 2520000
- flags = 1
- data = length 90, hash 96BDE594
- sample 70:
- time = 2530000
- flags = 1
- data = length 87, hash C261493C
- sample 71:
- time = 2540000
- flags = 1
- data = length 86, hash D037318E
- sample 72:
- time = 2550000
- flags = 1
- data = length 88, hash BC15BC88
- sample 73:
- time = 2560000
- flags = 1
- data = length 91, hash A8361A51
- sample 74:
- time = 2570000
- flags = 1
- data = length 87, hash 4AFDB5F2
- sample 75:
- time = 2580000
- flags = 1
- data = length 87, hash 6447F8CB
- sample 76:
- time = 2590000
- flags = 1
- data = length 89, hash 48305229
- sample 77:
- time = 2600000
- flags = 1
- data = length 87, hash 8741D9E7
- sample 78:
- time = 2610000
- flags = 1
- data = length 120, hash 761F020C
- sample 79:
- time = 2620000
- flags = 1
- data = length 139, hash AECE2E57
- sample 80:
- time = 2630000
- flags = 1
- data = length 166, hash 6288797A
- sample 81:
- time = 2640000
- flags = 1
- data = length 144, hash 437821A0
- sample 82:
- time = 2650000
- flags = 1
- data = length 113, hash FCCBEDF1
- sample 83:
- time = 2660000
- flags = 1
- data = length 108, hash C4040614
- sample 84:
- time = 2670000
- flags = 1
- data = length 125, hash E29064C2
- sample 85:
- time = 2680000
- flags = 1
- data = length 126, hash D42D24FF
- sample 86:
- time = 2690000
- flags = 1
- data = length 122, hash 30AF267D
- sample 87:
- time = 2700000
- flags = 1
- data = length 122, hash 45CEC1FB
- sample 88:
- time = 2710000
- flags = 1
- data = length 134, hash 59143FE2
- sample 89:
- time = 2720000
- flags = 1
- data = length 134, hash BD52A84
- sample 90:
- time = 2730000
- flags = 1
- data = length 120, hash 745C3714
- sample 91:
- time = 2740000
- flags = 1
- data = length 126, hash 505E117B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus.3.dump b/tree/library/extractor/src/test/assets/ogg/bear.opus.3.dump
deleted file mode 100644
index c19eb7e..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear.opus.3.dump
+++ /dev/null
@@ -1,38 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2747500
- getPosition(0) = [[timeUs=0, position=125]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/opus
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 19, hash BFE794DB
- data = length 8, hash CA22068C
- data = length 8, hash 79C07075
- total output bytes = 126
- sample count = 1
- sample 0:
- time = 2741000
- flags = 1
- data = length 126, hash 505E117B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus.unklen.dump b/tree/library/extractor/src/test/assets/ogg/bear.opus.unklen.dump
deleted file mode 100644
index 55d08fc..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear.opus.unklen.dump
+++ /dev/null
@@ -1,1134 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = audio/opus
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 19, hash BFE794DB
- data = length 8, hash CA22068C
- data = length 8, hash 79C07075
- total output bytes = 25541
- sample count = 275
- sample 0:
- time = 0
- flags = 1
- data = length 234, hash B77BFFDA
- sample 1:
- time = 10000
- flags = 1
- data = length 165, hash F7B07C35
- sample 2:
- time = 20000
- flags = 1
- data = length 100, hash 21AFA81F
- sample 3:
- time = 30000
- flags = 1
- data = length 85, hash 9180DC2F
- sample 4:
- time = 40000
- flags = 1
- data = length 85, hash 6AE958C
- sample 5:
- time = 50000
- flags = 1
- data = length 86, hash C1C5AE60
- sample 6:
- time = 60000
- flags = 1
- data = length 87, hash B9BD2620
- sample 7:
- time = 70000
- flags = 1
- data = length 86, hash 5E69E6F9
- sample 8:
- time = 80000
- flags = 1
- data = length 90, hash C44C7DD9
- sample 9:
- time = 90000
- flags = 1
- data = length 86, hash C3FCDC6F
- sample 10:
- time = 100000
- flags = 1
- data = length 86, hash 44EA03BA
- sample 11:
- time = 110000
- flags = 1
- data = length 160, hash 9F4E1AE8
- sample 12:
- time = 120000
- flags = 1
- data = length 89, hash 31234460
- sample 13:
- time = 130000
- flags = 1
- data = length 91, hash 45012D79
- sample 14:
- time = 140000
- flags = 1
- data = length 90, hash B3E3AC75
- sample 15:
- time = 150000
- flags = 1
- data = length 87, hash B83B756B
- sample 16:
- time = 160000
- flags = 1
- data = length 86, hash 383921EB
- sample 17:
- time = 170000
- flags = 1
- data = length 97, hash 959AD270
- sample 18:
- time = 180000
- flags = 1
- data = length 92, hash 46C74FA8
- sample 19:
- time = 190000
- flags = 1
- data = length 91, hash CEA401DD
- sample 20:
- time = 200000
- flags = 1
- data = length 89, hash 48C41853
- sample 21:
- time = 210000
- flags = 1
- data = length 90, hash F23245BD
- sample 22:
- time = 220000
- flags = 1
- data = length 96, hash 95E8985D
- sample 23:
- time = 230000
- flags = 1
- data = length 96, hash 34295492
- sample 24:
- time = 240000
- flags = 1
- data = length 94, hash 4E3C9C0F
- sample 25:
- time = 250000
- flags = 1
- data = length 89, hash 28B74A29
- sample 26:
- time = 260000
- flags = 1
- data = length 87, hash BAC119A7
- sample 27:
- time = 270000
- flags = 1
- data = length 88, hash 7139FF38
- sample 28:
- time = 280000
- flags = 1
- data = length 85, hash 246E1D2A
- sample 29:
- time = 290000
- flags = 1
- data = length 86, hash 488A0900
- sample 30:
- time = 300000
- flags = 1
- data = length 90, hash 16FD17B1
- sample 31:
- time = 310000
- flags = 1
- data = length 87, hash 20E849D9
- sample 32:
- time = 320000
- flags = 1
- data = length 86, hash 23A0E9BA
- sample 33:
- time = 330000
- flags = 1
- data = length 87, hash EC935537
- sample 34:
- time = 340000
- flags = 1
- data = length 92, hash 4D9935AD
- sample 35:
- time = 350000
- flags = 1
- data = length 87, hash DEDE3FA
- sample 36:
- time = 360000
- flags = 1
- data = length 87, hash ADC25A6E
- sample 37:
- time = 370000
- flags = 1
- data = length 88, hash A1C828C5
- sample 38:
- time = 380000
- flags = 1
- data = length 89, hash 735C087A
- sample 39:
- time = 390000
- flags = 1
- data = length 89, hash 19AF5D10
- sample 40:
- time = 400000
- flags = 1
- data = length 90, hash BCCEA2BB
- sample 41:
- time = 410000
- flags = 1
- data = length 86, hash A7C934A0
- sample 42:
- time = 420000
- flags = 1
- data = length 86, hash 28BBC0A8
- sample 43:
- time = 430000
- flags = 1
- data = length 85, hash E60BB12D
- sample 44:
- time = 440000
- flags = 1
- data = length 141, hash 1D2B8920
- sample 45:
- time = 450000
- flags = 1
- data = length 121, hash 8AA3E19C
- sample 46:
- time = 460000
- flags = 1
- data = length 86, hash 24DF0F37
- sample 47:
- time = 470000
- flags = 1
- data = length 86, hash 1D1775FF
- sample 48:
- time = 480000
- flags = 1
- data = length 87, hash 5230399E
- sample 49:
- time = 490000
- flags = 1
- data = length 91, hash 6CD98305
- sample 50:
- time = 500000
- flags = 1
- data = length 88, hash 4069FBB
- sample 51:
- time = 510000
- flags = 1
- data = length 89, hash 76824ABF
- sample 52:
- time = 520000
- flags = 1
- data = length 87, hash BC1B1322
- sample 53:
- time = 530000
- flags = 1
- data = length 102, hash E01BA053
- sample 54:
- time = 540000
- flags = 1
- data = length 85, hash C09B626D
- sample 55:
- time = 550000
- flags = 1
- data = length 88, hash 6B7B404A
- sample 56:
- time = 560000
- flags = 1
- data = length 85, hash 74A15DC7
- sample 57:
- time = 570000
- flags = 1
- data = length 88, hash 38DB82E5
- sample 58:
- time = 580000
- flags = 1
- data = length 86, hash 1A39C081
- sample 59:
- time = 590000
- flags = 1
- data = length 87, hash 39FEC92
- sample 60:
- time = 600000
- flags = 1
- data = length 92, hash 278EA09
- sample 61:
- time = 610000
- flags = 1
- data = length 87, hash 28265F2D
- sample 62:
- time = 620000
- flags = 1
- data = length 86, hash CC2040C6
- sample 63:
- time = 630000
- flags = 1
- data = length 138, hash 9E07BC1F
- sample 64:
- time = 640000
- flags = 1
- data = length 85, hash 4F299670
- sample 65:
- time = 650000
- flags = 1
- data = length 125, hash B61123C3
- sample 66:
- time = 660000
- flags = 1
- data = length 89, hash 5CC688ED
- sample 67:
- time = 670000
- flags = 1
- data = length 88, hash 84AF64A6
- sample 68:
- time = 680000
- flags = 1
- data = length 89, hash A9BFC8DC
- sample 69:
- time = 690000
- flags = 1
- data = length 90, hash 2FF77060
- sample 70:
- time = 700000
- flags = 1
- data = length 96, hash E11AFD61
- sample 71:
- time = 710000
- flags = 1
- data = length 87, hash 85D14EDA
- sample 72:
- time = 720000
- flags = 1
- data = length 88, hash 5FC71D53
- sample 73:
- time = 730000
- flags = 1
- data = length 89, hash 957187B6
- sample 74:
- time = 740000
- flags = 1
- data = length 89, hash 5A776082
- sample 75:
- time = 750000
- flags = 1
- data = length 87, hash E8A83AFE
- sample 76:
- time = 760000
- flags = 1
- data = length 87, hash F1989133
- sample 77:
- time = 770000
- flags = 1
- data = length 87, hash FA06BCCA
- sample 78:
- time = 780000
- flags = 1
- data = length 86, hash BD97E0C0
- sample 79:
- time = 790000
- flags = 1
- data = length 88, hash E6AE022C
- sample 80:
- time = 800000
- flags = 1
- data = length 87, hash FB6C6169
- sample 81:
- time = 810000
- flags = 1
- data = length 87, hash DFFCD2CF
- sample 82:
- time = 820000
- flags = 1
- data = length 88, hash A4B5EB52
- sample 83:
- time = 830000
- flags = 1
- data = length 85, hash A63CF4EA
- sample 84:
- time = 840000
- flags = 1
- data = length 86, hash F126E7C7
- sample 85:
- time = 850000
- flags = 1
- data = length 86, hash 21A8B22F
- sample 86:
- time = 860000
- flags = 1
- data = length 87, hash 6520E7C1
- sample 87:
- time = 870000
- flags = 1
- data = length 88, hash 825B7423
- sample 88:
- time = 880000
- flags = 1
- data = length 88, hash DF3DBD48
- sample 89:
- time = 890000
- flags = 1
- data = length 87, hash B32C68D0
- sample 90:
- time = 900000
- flags = 1
- data = length 89, hash B99DFFCA
- sample 91:
- time = 910000
- flags = 1
- data = length 88, hash 9C8D5178
- sample 92:
- time = 920000
- flags = 1
- data = length 88, hash 48A0B19A
- sample 93:
- time = 930000
- flags = 1
- data = length 88, hash B62C94DD
- sample 94:
- time = 940000
- flags = 1
- data = length 92, hash 96DBDD46
- sample 95:
- time = 950000
- flags = 1
- data = length 87, hash 7B80E6F
- sample 96:
- time = 960000
- flags = 1
- data = length 86, hash 9C60225B
- sample 97:
- time = 970000
- flags = 1
- data = length 87, hash 45F7E6E8
- sample 98:
- time = 980000
- flags = 1
- data = length 87, hash DDC2D592
- sample 99:
- time = 990000
- flags = 1
- data = length 91, hash 173D3B26
- sample 100:
- time = 1000000
- flags = 1
- data = length 87, hash CF3629DF
- sample 101:
- time = 1010000
- flags = 1
- data = length 87, hash BBE2E7B3
- sample 102:
- time = 1020000
- flags = 1
- data = length 89, hash 89AFFB10
- sample 103:
- time = 1030000
- flags = 1
- data = length 88, hash 510DCC90
- sample 104:
- time = 1040000
- flags = 1
- data = length 88, hash CBA56E5F
- sample 105:
- time = 1050000
- flags = 1
- data = length 87, hash B4B1B3FF
- sample 106:
- time = 1060000
- flags = 1
- data = length 89, hash B976A537
- sample 107:
- time = 1070000
- flags = 1
- data = length 96, hash 43ECF2C1
- sample 108:
- time = 1080000
- flags = 1
- data = length 90, hash BB7ECB44
- sample 109:
- time = 1090000
- flags = 1
- data = length 89, hash B8E221A5
- sample 110:
- time = 1100000
- flags = 1
- data = length 86, hash B35BEF5B
- sample 111:
- time = 1110000
- flags = 1
- data = length 89, hash 9002F0EC
- sample 112:
- time = 1120000
- flags = 1
- data = length 85, hash F694B20
- sample 113:
- time = 1130000
- flags = 1
- data = length 87, hash D7CC386E
- sample 114:
- time = 1140000
- flags = 1
- data = length 89, hash EE9E0E79
- sample 115:
- time = 1150000
- flags = 1
- data = length 90, hash CA72C96B
- sample 116:
- time = 1160000
- flags = 1
- data = length 112, hash 4AD3D1B1
- sample 117:
- time = 1170000
- flags = 1
- data = length 87, hash FA568FAB
- sample 118:
- time = 1180000
- flags = 1
- data = length 90, hash 6E6948D2
- sample 119:
- time = 1190000
- flags = 1
- data = length 89, hash 5465A762
- sample 120:
- time = 1200000
- flags = 1
- data = length 87, hash BED19B40
- sample 121:
- time = 1210000
- flags = 1
- data = length 89, hash 5D05836A
- sample 122:
- time = 1220000
- flags = 1
- data = length 87, hash A8A3EF5A
- sample 123:
- time = 1230000
- flags = 1
- data = length 90, hash 6425A77A
- sample 124:
- time = 1240000
- flags = 1
- data = length 92, hash 7F320FA
- sample 125:
- time = 1250000
- flags = 1
- data = length 89, hash 2C7837D6
- sample 126:
- time = 1260000
- flags = 1
- data = length 86, hash 58D56685
- sample 127:
- time = 1270000
- flags = 1
- data = length 91, hash ADC5072F
- sample 128:
- time = 1280000
- flags = 1
- data = length 85, hash 53EFD93
- sample 129:
- time = 1290000
- flags = 1
- data = length 87, hash D006B535
- sample 130:
- time = 1300000
- flags = 1
- data = length 86, hash AE944625
- sample 131:
- time = 1310000
- flags = 1
- data = length 89, hash B5D3C81D
- sample 132:
- time = 1320000
- flags = 1
- data = length 86, hash 3BB1D0E7
- sample 133:
- time = 1330000
- flags = 1
- data = length 102, hash 16EEC441
- sample 134:
- time = 1340000
- flags = 1
- data = length 90, hash 1005B936
- sample 135:
- time = 1350000
- flags = 1
- data = length 85, hash 15EEBF9A
- sample 136:
- time = 1360000
- flags = 1
- data = length 87, hash 37C83AC2
- sample 137:
- time = 1370000
- flags = 1
- data = length 85, hash 2D27855D
- sample 138:
- time = 1380000
- flags = 1
- data = length 85, hash 753EB7C6
- sample 139:
- time = 1390000
- flags = 1
- data = length 91, hash C0813318
- sample 140:
- time = 1400000
- flags = 1
- data = length 89, hash 3A6468AC
- sample 141:
- time = 1410000
- flags = 1
- data = length 88, hash 3D220ABC
- sample 142:
- time = 1420000
- flags = 1
- data = length 140, hash 7949ABC7
- sample 143:
- time = 1430000
- flags = 1
- data = length 92, hash F19AFA45
- sample 144:
- time = 1440000
- flags = 1
- data = length 90, hash 3D21587C
- sample 145:
- time = 1450000
- flags = 1
- data = length 89, hash 5C12226C
- sample 146:
- time = 1460000
- flags = 1
- data = length 90, hash 22BA14FC
- sample 147:
- time = 1470000
- flags = 1
- data = length 88, hash F064B21C
- sample 148:
- time = 1480000
- flags = 1
- data = length 87, hash 6D7906B9
- sample 149:
- time = 1490000
- flags = 1
- data = length 88, hash 6756A484
- sample 150:
- time = 1500000
- flags = 1
- data = length 91, hash C95C00B6
- sample 151:
- time = 1510000
- flags = 1
- data = length 87, hash 728D8119
- sample 152:
- time = 1520000
- flags = 1
- data = length 90, hash C43DA1B4
- sample 153:
- time = 1530000
- flags = 1
- data = length 88, hash C181BB57
- sample 154:
- time = 1540000
- flags = 1
- data = length 84, hash F75B1639
- sample 155:
- time = 1550000
- flags = 1
- data = length 87, hash B6F32978
- sample 156:
- time = 1560000
- flags = 1
- data = length 90, hash 36D6E2D7
- sample 157:
- time = 1570000
- flags = 1
- data = length 87, hash 4C9657A7
- sample 158:
- time = 1580000
- flags = 1
- data = length 89, hash C3BDB9B7
- sample 159:
- time = 1590000
- flags = 1
- data = length 88, hash DB51087E
- sample 160:
- time = 1600000
- flags = 1
- data = length 86, hash 1550F998
- sample 161:
- time = 1610000
- flags = 1
- data = length 86, hash A445FAD4
- sample 162:
- time = 1620000
- flags = 1
- data = length 85, hash 60D3362C
- sample 163:
- time = 1630000
- flags = 1
- data = length 172, hash 945D63E4
- sample 164:
- time = 1640000
- flags = 1
- data = length 107, hash 585B7C04
- sample 165:
- time = 1650000
- flags = 1
- data = length 110, hash 74BECF69
- sample 166:
- time = 1660000
- flags = 1
- data = length 87, hash 63DE1D24
- sample 167:
- time = 1670000
- flags = 1
- data = length 90, hash 1C1D28DB
- sample 168:
- time = 1680000
- flags = 1
- data = length 87, hash CB382A67
- sample 169:
- time = 1690000
- flags = 1
- data = length 85, hash A227BA13
- sample 170:
- time = 1700000
- flags = 1
- data = length 86, hash EFD8B10B
- sample 171:
- time = 1710000
- flags = 1
- data = length 87, hash 47FF364A
- sample 172:
- time = 1720000
- flags = 1
- data = length 91, hash 31D4B48A
- sample 173:
- time = 1730000
- flags = 1
- data = length 91, hash DD69BD85
- sample 174:
- time = 1740000
- flags = 1
- data = length 88, hash AF1A95C6
- sample 175:
- time = 1750000
- flags = 1
- data = length 87, hash 2FB8AF74
- sample 176:
- time = 1760000
- flags = 1
- data = length 92, hash 173C707A
- sample 177:
- time = 1770000
- flags = 1
- data = length 88, hash 5F58F5E8
- sample 178:
- time = 1780000
- flags = 1
- data = length 91, hash D449785F
- sample 179:
- time = 1790000
- flags = 1
- data = length 91, hash CE2CB465
- sample 180:
- time = 1800000
- flags = 1
- data = length 93, hash ABC1C62E
- sample 181:
- time = 1810000
- flags = 1
- data = length 87, hash 83B4B9A0
- sample 182:
- time = 1820000
- flags = 1
- data = length 87, hash 3220D562
- sample 183:
- time = 1830000
- flags = 1
- data = length 86, hash 64D21AA1
- sample 184:
- time = 1840000
- flags = 1
- data = length 86, hash A1FAAF2C
- sample 185:
- time = 1850000
- flags = 1
- data = length 86, hash ECA80F7E
- sample 186:
- time = 1860000
- flags = 1
- data = length 86, hash FEB03B2C
- sample 187:
- time = 1870000
- flags = 1
- data = length 85, hash 2C2E6B2F
- sample 188:
- time = 1880000
- flags = 1
- data = length 89, hash A0D7AC3
- sample 189:
- time = 1890000
- flags = 1
- data = length 87, hash 83739547
- sample 190:
- time = 1900000
- flags = 1
- data = length 86, hash 991E531E
- sample 191:
- time = 1910000
- flags = 1
- data = length 88, hash 16B287A3
- sample 192:
- time = 1920000
- flags = 1
- data = length 86, hash FC86EED
- sample 193:
- time = 1930000
- flags = 1
- data = length 86, hash 96AF0248
- sample 194:
- time = 1940000
- flags = 1
- data = length 86, hash 288402C8
- sample 195:
- time = 1950000
- flags = 1
- data = length 87, hash 4BBA7912
- sample 196:
- time = 1960000
- flags = 1
- data = length 86, hash 4A59C719
- sample 197:
- time = 1970000
- flags = 1
- data = length 85, hash 906E8187
- sample 198:
- time = 1980000
- flags = 1
- data = length 90, hash CB8B755D
- sample 199:
- time = 1990000
- flags = 1
- data = length 87, hash C8E02C
- sample 200:
- time = 2000000
- flags = 1
- data = length 88, hash ACF4D89A
- sample 201:
- time = 2010000
- flags = 1
- data = length 86, hash 510FE048
- sample 202:
- time = 2020000
- flags = 1
- data = length 86, hash 64983E46
- sample 203:
- time = 2030000
- flags = 1
- data = length 86, hash CEA76A1E
- sample 204:
- time = 2040000
- flags = 1
- data = length 87, hash AADE498E
- sample 205:
- time = 2050000
- flags = 1
- data = length 127, hash 353A6D8C
- sample 206:
- time = 2060000
- flags = 1
- data = length 87, hash 29E18E62
- sample 207:
- time = 2070000
- flags = 1
- data = length 87, hash 2CF7B30F
- sample 208:
- time = 2080000
- flags = 1
- data = length 94, hash 758704C3
- sample 209:
- time = 2090000
- flags = 1
- data = length 88, hash C2153A4C
- sample 210:
- time = 2100000
- flags = 1
- data = length 86, hash A0A83DA5
- sample 211:
- time = 2110000
- flags = 1
- data = length 86, hash 41017D7F
- sample 212:
- time = 2120000
- flags = 1
- data = length 93, hash 686B0CA2
- sample 213:
- time = 2130000
- flags = 1
- data = length 86, hash 554D16CC
- sample 214:
- time = 2140000
- flags = 1
- data = length 88, hash 99D72771
- sample 215:
- time = 2150000
- flags = 1
- data = length 88, hash 7176DFBF
- sample 216:
- time = 2160000
- flags = 1
- data = length 86, hash BAA22669
- sample 217:
- time = 2170000
- flags = 1
- data = length 88, hash B00B0D3C
- sample 218:
- time = 2180000
- flags = 1
- data = length 89, hash 73FED83A
- sample 219:
- time = 2190000
- flags = 1
- data = length 86, hash 4A4138D3
- sample 220:
- time = 2200000
- flags = 1
- data = length 89, hash E0A860FF
- sample 221:
- time = 2210000
- flags = 1
- data = length 95, hash EE5A8AED
- sample 222:
- time = 2220000
- flags = 1
- data = length 92, hash 36DBD7FD
- sample 223:
- time = 2230000
- flags = 1
- data = length 88, hash EE47A7E4
- sample 224:
- time = 2240000
- flags = 1
- data = length 100, hash 2E1A603F
- sample 225:
- time = 2250000
- flags = 1
- data = length 89, hash 657ED4A3
- sample 226:
- time = 2260000
- flags = 1
- data = length 86, hash A833DC7B
- sample 227:
- time = 2270000
- flags = 1
- data = length 88, hash 81E80732
- sample 228:
- time = 2280000
- flags = 1
- data = length 91, hash FA256A0F
- sample 229:
- time = 2290000
- flags = 1
- data = length 88, hash A63A4DBA
- sample 230:
- time = 2300000
- flags = 1
- data = length 88, hash 67910A9F
- sample 231:
- time = 2310000
- flags = 1
- data = length 86, hash EB387DB6
- sample 232:
- time = 2320000
- flags = 1
- data = length 88, hash 5ACAAC2A
- sample 233:
- time = 2330000
- flags = 1
- data = length 86, hash 6ADF2E1F
- sample 234:
- time = 2340000
- flags = 1
- data = length 85, hash 9D064471
- sample 235:
- time = 2350000
- flags = 1
- data = length 87, hash F176C59
- sample 236:
- time = 2360000
- flags = 1
- data = length 89, hash 5CA40CE4
- sample 237:
- time = 2370000
- flags = 1
- data = length 88, hash 67B944FC
- sample 238:
- time = 2380000
- flags = 1
- data = length 86, hash B3A84EC8
- sample 239:
- time = 2390000
- flags = 1
- data = length 92, hash A6ACF94B
- sample 240:
- time = 2400000
- flags = 1
- data = length 88, hash CB0C9730
- sample 241:
- time = 2410000
- flags = 1
- data = length 88, hash C79FE804
- sample 242:
- time = 2420000
- flags = 1
- data = length 88, hash A74C7F0A
- sample 243:
- time = 2430000
- flags = 1
- data = length 91, hash 55F6F0A5
- sample 244:
- time = 2440000
- flags = 1
- data = length 93, hash 330F33E7
- sample 245:
- time = 2450000
- flags = 1
- data = length 89, hash 614AFBA0
- sample 246:
- time = 2460000
- flags = 1
- data = length 87, hash 3CE4652D
- sample 247:
- time = 2470000
- flags = 1
- data = length 87, hash 4EFD5467
- sample 248:
- time = 2480000
- flags = 1
- data = length 86, hash D81B3EB8
- sample 249:
- time = 2490000
- flags = 1
- data = length 88, hash 96CB6871
- sample 250:
- time = 2500000
- flags = 1
- data = length 88, hash E9DF2786
- sample 251:
- time = 2510000
- flags = 1
- data = length 89, hash 2CA33D96
- sample 252:
- time = 2520000
- flags = 1
- data = length 90, hash 96BDE594
- sample 253:
- time = 2530000
- flags = 1
- data = length 87, hash C261493C
- sample 254:
- time = 2540000
- flags = 1
- data = length 86, hash D037318E
- sample 255:
- time = 2550000
- flags = 1
- data = length 88, hash BC15BC88
- sample 256:
- time = 2560000
- flags = 1
- data = length 91, hash A8361A51
- sample 257:
- time = 2570000
- flags = 1
- data = length 87, hash 4AFDB5F2
- sample 258:
- time = 2580000
- flags = 1
- data = length 87, hash 6447F8CB
- sample 259:
- time = 2590000
- flags = 1
- data = length 89, hash 48305229
- sample 260:
- time = 2600000
- flags = 1
- data = length 87, hash 8741D9E7
- sample 261:
- time = 2610000
- flags = 1
- data = length 120, hash 761F020C
- sample 262:
- time = 2620000
- flags = 1
- data = length 139, hash AECE2E57
- sample 263:
- time = 2630000
- flags = 1
- data = length 166, hash 6288797A
- sample 264:
- time = 2640000
- flags = 1
- data = length 144, hash 437821A0
- sample 265:
- time = 2650000
- flags = 1
- data = length 113, hash FCCBEDF1
- sample 266:
- time = 2660000
- flags = 1
- data = length 108, hash C4040614
- sample 267:
- time = 2670000
- flags = 1
- data = length 125, hash E29064C2
- sample 268:
- time = 2680000
- flags = 1
- data = length 126, hash D42D24FF
- sample 269:
- time = 2690000
- flags = 1
- data = length 122, hash 30AF267D
- sample 270:
- time = 2700000
- flags = 1
- data = length 122, hash 45CEC1FB
- sample 271:
- time = 2710000
- flags = 1
- data = length 134, hash 59143FE2
- sample 272:
- time = 2720000
- flags = 1
- data = length 134, hash BD52A84
- sample 273:
- time = 2730000
- flags = 1
- data = length 120, hash 745C3714
- sample 274:
- time = 2740000
- flags = 1
- data = length 126, hash 505E117B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.0.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.0.dump
deleted file mode 100644
index d323426..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8457]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.1.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.1.dump
deleted file mode 100644
index 17e6c6d..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8457]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.2.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.2.dump
deleted file mode 100644
index e52b889..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8457]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.3.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.3.dump
deleted file mode 100644
index dabf555..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.3.dump
+++ /dev/null
@@ -1,36 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8457]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 445
- sample count = 1
- sample 0:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.unklen.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.unklen.dump
deleted file mode 100644
index d323426..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg.unklen.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8457]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump
deleted file mode 100644
index efbf8a3..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8407]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump
deleted file mode 100644
index 80ad204..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump
+++ /dev/null
@@ -1,124 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8407]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 113666
- sample count = 23
- sample 0:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 1:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 2:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 3:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 4:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 5:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 6:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 7:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 8:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 9:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 10:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 11:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 12:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 13:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 14:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 15:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 16:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 17:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 18:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 19:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 20:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 21:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 22:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump
deleted file mode 100644
index c2efd50..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump
+++ /dev/null
@@ -1,80 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8407]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 55652
- sample count = 12
- sample 0:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 1:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 2:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 3:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 4:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 5:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 6:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 7:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 8:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 9:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 10:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 11:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump
deleted file mode 100644
index 2660123..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump
+++ /dev/null
@@ -1,36 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=8407]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 445
- sample count = 1
- sample 0:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.unklen.dump b/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.unklen.dump
deleted file mode 100644
index 67a1fec..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg.unklen.dump
+++ /dev/null
@@ -1,164 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 1536000
- id = null
- containerMimeType = null
- sampleMimeType = audio/flac
- maxInputSize = 5776
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 42, hash 83F6895
- total output bytes = 164431
- sample count = 33
- sample 0:
- time = 0
- flags = 1
- data = length 5030, hash D2B60530
- sample 1:
- time = 85333
- flags = 1
- data = length 5066, hash 4C932A54
- sample 2:
- time = 170666
- flags = 1
- data = length 5112, hash 7E5A7B61
- sample 3:
- time = 256000
- flags = 1
- data = length 5044, hash 7EF93F13
- sample 4:
- time = 341333
- flags = 1
- data = length 4943, hash DE7E27F8
- sample 5:
- time = 426666
- flags = 1
- data = length 5121, hash 6D0D0B40
- sample 6:
- time = 512000
- flags = 1
- data = length 5068, hash 9924644F
- sample 7:
- time = 597333
- flags = 1
- data = length 5143, hash 6C34F0CE
- sample 8:
- time = 682666
- flags = 1
- data = length 5109, hash E3B7BEFB
- sample 9:
- time = 768000
- flags = 1
- data = length 5129, hash 44111D9B
- sample 10:
- time = 853333
- flags = 1
- data = length 5031, hash 9D55EA53
- sample 11:
- time = 938666
- flags = 1
- data = length 5119, hash E1CB9BA6
- sample 12:
- time = 1024000
- flags = 1
- data = length 5360, hash 17265C5D
- sample 13:
- time = 1109333
- flags = 1
- data = length 5340, hash A90FDDF1
- sample 14:
- time = 1194666
- flags = 1
- data = length 5162, hash 31F65AD5
- sample 15:
- time = 1280000
- flags = 1
- data = length 5168, hash F2394F2D
- sample 16:
- time = 1365333
- flags = 1
- data = length 5776, hash 58437AB3
- sample 17:
- time = 1450666
- flags = 1
- data = length 5394, hash EBAB20A8
- sample 18:
- time = 1536000
- flags = 1
- data = length 5168, hash BF37C7A5
- sample 19:
- time = 1621333
- flags = 1
- data = length 5324, hash 59546B7B
- sample 20:
- time = 1706666
- flags = 1
- data = length 5172, hash 6036EF0B
- sample 21:
- time = 1792000
- flags = 1
- data = length 5102, hash 5A131071
- sample 22:
- time = 1877333
- flags = 1
- data = length 5111, hash 3D9EBB3B
- sample 23:
- time = 1962666
- flags = 1
- data = length 5113, hash 61101D4F
- sample 24:
- time = 2048000
- flags = 1
- data = length 5229, hash D2E55742
- sample 25:
- time = 2133333
- flags = 1
- data = length 5162, hash 7F2E97FA
- sample 26:
- time = 2218666
- flags = 1
- data = length 5255, hash D92A782
- sample 27:
- time = 2304000
- flags = 1
- data = length 5196, hash 98FE5138
- sample 28:
- time = 2389333
- flags = 1
- data = length 5214, hash 3D35C38C
- sample 29:
- time = 2474666
- flags = 1
- data = length 5211, hash 7E25420F
- sample 30:
- time = 2560000
- flags = 1
- data = length 5230, hash 2AD96FBC
- sample 31:
- time = 2645333
- flags = 1
- data = length 3384, hash 938BCDD9
- sample 32:
- time = 2730666
- flags = 1
- data = length 445, hash A388E3D6
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.0.dump b/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.0.dump
deleted file mode 100644
index 2ce3f23..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.0.dump
+++ /dev/null
@@ -1,753 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=3995]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 112000
- id = null
- containerMimeType = null
- sampleMimeType = audio/vorbis
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash 9A8FF207
- data = length 3832, hash 8A406249
- total output bytes = 26873
- sample count = 180
- sample 0:
- time = 0
- flags = 1
- data = length 49, hash 2FFF94F0
- sample 1:
- time = 0
- flags = 1
- data = length 44, hash 3946418A
- sample 2:
- time = 2666
- flags = 1
- data = length 55, hash 2A0B878E
- sample 3:
- time = 5333
- flags = 1
- data = length 53, hash CC3B6879
- sample 4:
- time = 8000
- flags = 1
- data = length 215, hash 106AE950
- sample 5:
- time = 20000
- flags = 1
- data = length 192, hash 2B219F53
- sample 6:
- time = 41333
- flags = 1
- data = length 197, hash FBC39422
- sample 7:
- time = 62666
- flags = 1
- data = length 209, hash 386E8979
- sample 8:
- time = 84000
- flags = 1
- data = length 42, hash E81162C1
- sample 9:
- time = 96000
- flags = 1
- data = length 41, hash F15BEE36
- sample 10:
- time = 98666
- flags = 1
- data = length 42, hash D67EB19
- sample 11:
- time = 101333
- flags = 1
- data = length 42, hash F4DE4792
- sample 12:
- time = 104000
- flags = 1
- data = length 53, hash 80F66AC3
- sample 13:
- time = 106666
- flags = 1
- data = length 56, hash DCB9DFC4
- sample 14:
- time = 109333
- flags = 1
- data = length 55, hash 4E0C4E9D
- sample 15:
- time = 112000
- flags = 1
- data = length 203, hash 176B6862
- sample 16:
- time = 124000
- flags = 1
- data = length 193, hash AB13CB10
- sample 17:
- time = 145333
- flags = 1
- data = length 203, hash DE63DE9F
- sample 18:
- time = 166666
- flags = 1
- data = length 194, hash 4A9508A2
- sample 19:
- time = 188000
- flags = 1
- data = length 210, hash 196899B3
- sample 20:
- time = 209333
- flags = 1
- data = length 195, hash B68407F1
- sample 21:
- time = 230666
- flags = 1
- data = length 193, hash A1FA86E3
- sample 22:
- time = 252000
- flags = 1
- data = length 194, hash 5C0B9343
- sample 23:
- time = 273333
- flags = 1
- data = length 198, hash 789914B2
- sample 24:
- time = 294666
- flags = 1
- data = length 183, hash 1B82D11F
- sample 25:
- time = 316000
- flags = 1
- data = length 199, hash D5B848F4
- sample 26:
- time = 337333
- flags = 1
- data = length 192, hash B34427EA
- sample 27:
- time = 358666
- flags = 1
- data = length 199, hash C2599BB5
- sample 28:
- time = 380000
- flags = 1
- data = length 195, hash BFD83194
- sample 29:
- time = 401333
- flags = 1
- data = length 199, hash C9A7F7CA
- sample 30:
- time = 422666
- flags = 1
- data = length 44, hash 5D76EAD6
- sample 31:
- time = 434666
- flags = 1
- data = length 43, hash 8619C423
- sample 32:
- time = 437333
- flags = 1
- data = length 43, hash E490BBE
- sample 33:
- time = 440000
- flags = 1
- data = length 53, hash 8A557CAE
- sample 34:
- time = 442666
- flags = 1
- data = length 56, hash 81007BBA
- sample 35:
- time = 445333
- flags = 1
- data = length 56, hash 4E4DD67F
- sample 36:
- time = 448000
- flags = 1
- data = length 222, hash 414188AB
- sample 37:
- time = 460000
- flags = 1
- data = length 202, hash 67A07D30
- sample 38:
- time = 481333
- flags = 1
- data = length 200, hash E357D853
- sample 39:
- time = 502666
- flags = 1
- data = length 203, hash 4653DC90
- sample 40:
- time = 524000
- flags = 1
- data = length 192, hash A65E6C09
- sample 41:
- time = 545333
- flags = 1
- data = length 202, hash FBEAC508
- sample 42:
- time = 566666
- flags = 1
- data = length 202, hash E9B7B59F
- sample 43:
- time = 588000
- flags = 1
- data = length 204, hash E24AA78E
- sample 44:
- time = 609333
- flags = 1
- data = length 41, hash 3FBC5216
- sample 45:
- time = 621333
- flags = 1
- data = length 47, hash 153FBC55
- sample 46:
- time = 624000
- flags = 1
- data = length 42, hash 2B493D6C
- sample 47:
- time = 626666
- flags = 1
- data = length 42, hash 8303BEE3
- sample 48:
- time = 629333
- flags = 1
- data = length 62, hash 71AEE50B
- sample 49:
- time = 632000
- flags = 1
- data = length 54, hash 52F61908
- sample 50:
- time = 634666
- flags = 1
- data = length 45, hash 7BD3E3A1
- sample 51:
- time = 637333
- flags = 1
- data = length 41, hash E0F65472
- sample 52:
- time = 640000
- flags = 1
- data = length 45, hash 41838675
- sample 53:
- time = 642666
- flags = 1
- data = length 44, hash FCBC2147
- sample 54:
- time = 645333
- flags = 1
- data = length 45, hash 1A5987E3
- sample 55:
- time = 648000
- flags = 1
- data = length 43, hash 99074864
- sample 56:
- time = 650666
- flags = 1
- data = length 57, hash D4A9B60A
- sample 57:
- time = 653333
- flags = 1
- data = length 52, hash 302129DA
- sample 58:
- time = 656000
- flags = 1
- data = length 57, hash D8DD99C0
- sample 59:
- time = 658666
- flags = 1
- data = length 206, hash F4B9EF26
- sample 60:
- time = 670666
- flags = 1
- data = length 197, hash 7B8ACC8A
- sample 61:
- time = 692000
- flags = 1
- data = length 186, hash 161027CB
- sample 62:
- time = 713333
- flags = 1
- data = length 186, hash 1D6871B6
- sample 63:
- time = 734666
- flags = 1
- data = length 201, hash 536E9FDB
- sample 64:
- time = 756000
- flags = 1
- data = length 192, hash D38EFAC5
- sample 65:
- time = 777333
- flags = 1
- data = length 194, hash 4B394EF3
- sample 66:
- time = 798666
- flags = 1
- data = length 206, hash 1B31BA99
- sample 67:
- time = 820000
- flags = 1
- data = length 212, hash AD061F43
- sample 68:
- time = 841333
- flags = 1
- data = length 180, hash 6D1F7481
- sample 69:
- time = 862666
- flags = 1
- data = length 195, hash D80B21F
- sample 70:
- time = 884000
- flags = 1
- data = length 186, hash D367882
- sample 71:
- time = 905333
- flags = 1
- data = length 195, hash 2722159A
- sample 72:
- time = 926666
- flags = 1
- data = length 199, hash 10CEE97A
- sample 73:
- time = 948000
- flags = 1
- data = length 191, hash 2CF9FB3F
- sample 74:
- time = 969333
- flags = 1
- data = length 197, hash A725DA0
- sample 75:
- time = 990666
- flags = 1
- data = length 211, hash D4E5DB9E
- sample 76:
- time = 1012000
- flags = 1
- data = length 189, hash 1A90F496
- sample 77:
- time = 1033333
- flags = 1
- data = length 187, hash 44DB2689
- sample 78:
- time = 1054666
- flags = 1
- data = length 197, hash 6D3E5117
- sample 79:
- time = 1076000
- flags = 1
- data = length 208, hash 5B57B288
- sample 80:
- time = 1097333
- flags = 1
- data = length 198, hash D5FC05
- sample 81:
- time = 1118666
- flags = 1
- data = length 192, hash 350BBA45
- sample 82:
- time = 1140000
- flags = 1
- data = length 195, hash 5F96F2A8
- sample 83:
- time = 1161333
- flags = 1
- data = length 202, hash 61D7CC33
- sample 84:
- time = 1182666
- flags = 1
- data = length 202, hash 49D335F2
- sample 85:
- time = 1204000
- flags = 1
- data = length 192, hash 2FE9CB1A
- sample 86:
- time = 1225333
- flags = 1
- data = length 201, hash BF0763B2
- sample 87:
- time = 1246666
- flags = 1
- data = length 184, hash AD047421
- sample 88:
- time = 1268000
- flags = 1
- data = length 196, hash F9088F14
- sample 89:
- time = 1289333
- flags = 1
- data = length 190, hash AC6D38FD
- sample 90:
- time = 1310666
- flags = 1
- data = length 195, hash 8D1A66D2
- sample 91:
- time = 1332000
- flags = 1
- data = length 197, hash B46BFB6B
- sample 92:
- time = 1353333
- flags = 1
- data = length 195, hash D9761F23
- sample 93:
- time = 1374666
- flags = 1
- data = length 204, hash 3391B617
- sample 94:
- time = 1396000
- flags = 1
- data = length 42, hash 33A1FB52
- sample 95:
- time = 1408000
- flags = 1
- data = length 44, hash 408B146E
- sample 96:
- time = 1410666
- flags = 1
- data = length 44, hash 171C7E0D
- sample 97:
- time = 1413333
- flags = 1
- data = length 54, hash 6307E16C
- sample 98:
- time = 1416000
- flags = 1
- data = length 53, hash 4A319572
- sample 99:
- time = 1418666
- flags = 1
- data = length 215, hash BA9C445C
- sample 100:
- time = 1430666
- flags = 1
- data = length 201, hash 3120D234
- sample 101:
- time = 1452000
- flags = 1
- data = length 187, hash DB44993C
- sample 102:
- time = 1473333
- flags = 1
- data = length 196, hash CF2002D7
- sample 103:
- time = 1494666
- flags = 1
- data = length 185, hash E03B5D7
- sample 104:
- time = 1516000
- flags = 1
- data = length 187, hash DA399A2C
- sample 105:
- time = 1537333
- flags = 1
- data = length 191, hash 292AA0DB
- sample 106:
- time = 1558666
- flags = 1
- data = length 201, hash 221910E0
- sample 107:
- time = 1580000
- flags = 1
- data = length 194, hash F4ED7821
- sample 108:
- time = 1601333
- flags = 1
- data = length 43, hash FDDA515E
- sample 109:
- time = 1613333
- flags = 1
- data = length 42, hash F3571C0A
- sample 110:
- time = 1616000
- flags = 1
- data = length 38, hash 39F910B3
- sample 111:
- time = 1618666
- flags = 1
- data = length 41, hash 2D189531
- sample 112:
- time = 1621333
- flags = 1
- data = length 43, hash 1F7574DB
- sample 113:
- time = 1624000
- flags = 1
- data = length 43, hash 644D15E5
- sample 114:
- time = 1626666
- flags = 1
- data = length 49, hash E8A0878
- sample 115:
- time = 1629333
- flags = 1
- data = length 55, hash DFF2046D
- sample 116:
- time = 1632000
- flags = 1
- data = length 49, hash 9FB8A23
- sample 117:
- time = 1634666
- flags = 1
- data = length 41, hash E3E15E3B
- sample 118:
- time = 1637333
- flags = 1
- data = length 42, hash E5D17A32
- sample 119:
- time = 1640000
- flags = 1
- data = length 42, hash F308B653
- sample 120:
- time = 1642666
- flags = 1
- data = length 55, hash BB750D76
- sample 121:
- time = 1645333
- flags = 1
- data = length 51, hash 96772ABF
- sample 122:
- time = 1648000
- flags = 1
- data = length 197, hash E4524346
- sample 123:
- time = 1660000
- flags = 1
- data = length 188, hash AC3E1BB5
- sample 124:
- time = 1681333
- flags = 1
- data = length 195, hash F56DB8A5
- sample 125:
- time = 1702666
- flags = 1
- data = length 198, hash C8970FF7
- sample 126:
- time = 1724000
- flags = 1
- data = length 202, hash AF425C68
- sample 127:
- time = 1745333
- flags = 1
- data = length 196, hash 4215D839
- sample 128:
- time = 1766666
- flags = 1
- data = length 204, hash DB9BE8E3
- sample 129:
- time = 1788000
- flags = 1
- data = length 206, hash E5B20AB8
- sample 130:
- time = 1809333
- flags = 1
- data = length 209, hash D7F47B95
- sample 131:
- time = 1830666
- flags = 1
- data = length 193, hash FB54FB05
- sample 132:
- time = 1852000
- flags = 1
- data = length 199, hash D99C3106
- sample 133:
- time = 1873333
- flags = 1
- data = length 206, hash 253885B9
- sample 134:
- time = 1894666
- flags = 1
- data = length 191, hash FBDD8162
- sample 135:
- time = 1916000
- flags = 1
- data = length 183, hash 7290332F
- sample 136:
- time = 1937333
- flags = 1
- data = length 189, hash 1A9DC3DE
- sample 137:
- time = 1958666
- flags = 1
- data = length 201, hash 5D936764
- sample 138:
- time = 1980000
- flags = 1
- data = length 193, hash 6B03E75E
- sample 139:
- time = 2001333
- flags = 1
- data = length 199, hash 8A21BA83
- sample 140:
- time = 2022666
- flags = 1
- data = length 41, hash E6362210
- sample 141:
- time = 2034666
- flags = 1
- data = length 43, hash 36A57B44
- sample 142:
- time = 2037333
- flags = 1
- data = length 43, hash E51797D5
- sample 143:
- time = 2040000
- flags = 1
- data = length 43, hash 1F336C72
- sample 144:
- time = 2042666
- flags = 1
- data = length 42, hash 201AD367
- sample 145:
- time = 2045333
- flags = 1
- data = length 50, hash 606CCD6
- sample 146:
- time = 2048000
- flags = 1
- data = length 56, hash B15EBD7A
- sample 147:
- time = 2050666
- flags = 1
- data = length 212, hash 273B8D22
- sample 148:
- time = 2062666
- flags = 1
- data = length 194, hash 44F9CE1
- sample 149:
- time = 2084000
- flags = 1
- data = length 195, hash EDF9EBA1
- sample 150:
- time = 2105333
- flags = 1
- data = length 194, hash CE9F2D26
- sample 151:
- time = 2126666
- flags = 1
- data = length 192, hash 204F8A23
- sample 152:
- time = 2148000
- flags = 1
- data = length 206, hash DFA57E67
- sample 153:
- time = 2169333
- flags = 1
- data = length 196, hash 3CF084AB
- sample 154:
- time = 2190666
- flags = 1
- data = length 202, hash 2AF75C08
- sample 155:
- time = 2212000
- flags = 1
- data = length 203, hash 748EAF7
- sample 156:
- time = 2233333
- flags = 1
- data = length 205, hash ED82379D
- sample 157:
- time = 2254666
- flags = 1
- data = length 193, hash 61F26F22
- sample 158:
- time = 2276000
- flags = 1
- data = length 189, hash 85EF1D20
- sample 159:
- time = 2297333
- flags = 1
- data = length 187, hash 25E41FBF
- sample 160:
- time = 2318666
- flags = 1
- data = length 199, hash F365808
- sample 161:
- time = 2340000
- flags = 1
- data = length 197, hash 94205329
- sample 162:
- time = 2361333
- flags = 1
- data = length 201, hash FA2B2055
- sample 163:
- time = 2382666
- flags = 1
- data = length 194, hash AF95381F
- sample 164:
- time = 2404000
- flags = 1
- data = length 201, hash 923D3534
- sample 165:
- time = 2425333
- flags = 1
- data = length 198, hash 35F84C2E
- sample 166:
- time = 2446666
- flags = 1
- data = length 204, hash 6642CA40
- sample 167:
- time = 2468000
- flags = 1
- data = length 183, hash 3E2DC6BE
- sample 168:
- time = 2489333
- flags = 1
- data = length 197, hash B1E458CE
- sample 169:
- time = 2510666
- flags = 1
- data = length 193, hash E9218C84
- sample 170:
- time = 2532000
- flags = 1
- data = length 192, hash FEF08D4B
- sample 171:
- time = 2553333
- flags = 1
- data = length 201, hash FC411147
- sample 172:
- time = 2574666
- flags = 1
- data = length 218, hash 86893464
- sample 173:
- time = 2596000
- flags = 1
- data = length 226, hash 31C5320
- sample 174:
- time = 2617333
- flags = 1
- data = length 233, hash 9432BEE5
- sample 175:
- time = 2638666
- flags = 1
- data = length 213, hash B3FCC53E
- sample 176:
- time = 2660000
- flags = 1
- data = length 204, hash D70DD5A2
- sample 177:
- time = 2681333
- flags = 1
- data = length 212, hash A4EF1B69
- sample 178:
- time = 2702666
- flags = 1
- data = length 203, hash 8B0748B5
- sample 179:
- time = 2724000
- flags = 1
- data = length 149, hash E455335B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.1.dump b/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.1.dump
deleted file mode 100644
index 4a8f664..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.1.dump
+++ /dev/null
@@ -1,469 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=3995]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 112000
- id = null
- containerMimeType = null
- sampleMimeType = audio/vorbis
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash 9A8FF207
- data = length 3832, hash 8A406249
- total output bytes = 17598
- sample count = 109
- sample 0:
- time = 896000
- flags = 1
- data = length 195, hash 2722159A
- sample 1:
- time = 917333
- flags = 1
- data = length 199, hash 10CEE97A
- sample 2:
- time = 938666
- flags = 1
- data = length 191, hash 2CF9FB3F
- sample 3:
- time = 960000
- flags = 1
- data = length 197, hash A725DA0
- sample 4:
- time = 981333
- flags = 1
- data = length 211, hash D4E5DB9E
- sample 5:
- time = 1002666
- flags = 1
- data = length 189, hash 1A90F496
- sample 6:
- time = 1024000
- flags = 1
- data = length 187, hash 44DB2689
- sample 7:
- time = 1045333
- flags = 1
- data = length 197, hash 6D3E5117
- sample 8:
- time = 1066666
- flags = 1
- data = length 208, hash 5B57B288
- sample 9:
- time = 1088000
- flags = 1
- data = length 198, hash D5FC05
- sample 10:
- time = 1109333
- flags = 1
- data = length 192, hash 350BBA45
- sample 11:
- time = 1130666
- flags = 1
- data = length 195, hash 5F96F2A8
- sample 12:
- time = 1152000
- flags = 1
- data = length 202, hash 61D7CC33
- sample 13:
- time = 1173333
- flags = 1
- data = length 202, hash 49D335F2
- sample 14:
- time = 1194666
- flags = 1
- data = length 192, hash 2FE9CB1A
- sample 15:
- time = 1216000
- flags = 1
- data = length 201, hash BF0763B2
- sample 16:
- time = 1237333
- flags = 1
- data = length 184, hash AD047421
- sample 17:
- time = 1258666
- flags = 1
- data = length 196, hash F9088F14
- sample 18:
- time = 1280000
- flags = 1
- data = length 190, hash AC6D38FD
- sample 19:
- time = 1301333
- flags = 1
- data = length 195, hash 8D1A66D2
- sample 20:
- time = 1322666
- flags = 1
- data = length 197, hash B46BFB6B
- sample 21:
- time = 1344000
- flags = 1
- data = length 195, hash D9761F23
- sample 22:
- time = 1365333
- flags = 1
- data = length 204, hash 3391B617
- sample 23:
- time = 1386666
- flags = 1
- data = length 42, hash 33A1FB52
- sample 24:
- time = 1398666
- flags = 1
- data = length 44, hash 408B146E
- sample 25:
- time = 1401333
- flags = 1
- data = length 44, hash 171C7E0D
- sample 26:
- time = 1404000
- flags = 1
- data = length 54, hash 6307E16C
- sample 27:
- time = 1406666
- flags = 1
- data = length 53, hash 4A319572
- sample 28:
- time = 1409333
- flags = 1
- data = length 215, hash BA9C445C
- sample 29:
- time = 1421333
- flags = 1
- data = length 201, hash 3120D234
- sample 30:
- time = 1442666
- flags = 1
- data = length 187, hash DB44993C
- sample 31:
- time = 1464000
- flags = 1
- data = length 196, hash CF2002D7
- sample 32:
- time = 1485333
- flags = 1
- data = length 185, hash E03B5D7
- sample 33:
- time = 1506666
- flags = 1
- data = length 187, hash DA399A2C
- sample 34:
- time = 1528000
- flags = 1
- data = length 191, hash 292AA0DB
- sample 35:
- time = 1549333
- flags = 1
- data = length 201, hash 221910E0
- sample 36:
- time = 1570666
- flags = 1
- data = length 194, hash F4ED7821
- sample 37:
- time = 1592000
- flags = 1
- data = length 43, hash FDDA515E
- sample 38:
- time = 1604000
- flags = 1
- data = length 42, hash F3571C0A
- sample 39:
- time = 1606666
- flags = 1
- data = length 38, hash 39F910B3
- sample 40:
- time = 1609333
- flags = 1
- data = length 41, hash 2D189531
- sample 41:
- time = 1612000
- flags = 1
- data = length 43, hash 1F7574DB
- sample 42:
- time = 1614666
- flags = 1
- data = length 43, hash 644D15E5
- sample 43:
- time = 1617333
- flags = 1
- data = length 49, hash E8A0878
- sample 44:
- time = 1620000
- flags = 1
- data = length 55, hash DFF2046D
- sample 45:
- time = 1622666
- flags = 1
- data = length 49, hash 9FB8A23
- sample 46:
- time = 1625333
- flags = 1
- data = length 41, hash E3E15E3B
- sample 47:
- time = 1628000
- flags = 1
- data = length 42, hash E5D17A32
- sample 48:
- time = 1630666
- flags = 1
- data = length 42, hash F308B653
- sample 49:
- time = 1633333
- flags = 1
- data = length 55, hash BB750D76
- sample 50:
- time = 1636000
- flags = 1
- data = length 51, hash 96772ABF
- sample 51:
- time = 1638666
- flags = 1
- data = length 197, hash E4524346
- sample 52:
- time = 1650666
- flags = 1
- data = length 188, hash AC3E1BB5
- sample 53:
- time = 1672000
- flags = 1
- data = length 195, hash F56DB8A5
- sample 54:
- time = 1693333
- flags = 1
- data = length 198, hash C8970FF7
- sample 55:
- time = 1714666
- flags = 1
- data = length 202, hash AF425C68
- sample 56:
- time = 1736000
- flags = 1
- data = length 196, hash 4215D839
- sample 57:
- time = 1757333
- flags = 1
- data = length 204, hash DB9BE8E3
- sample 58:
- time = 1778666
- flags = 1
- data = length 206, hash E5B20AB8
- sample 59:
- time = 1800000
- flags = 1
- data = length 209, hash D7F47B95
- sample 60:
- time = 1821333
- flags = 1
- data = length 193, hash FB54FB05
- sample 61:
- time = 1842666
- flags = 1
- data = length 199, hash D99C3106
- sample 62:
- time = 1864000
- flags = 1
- data = length 206, hash 253885B9
- sample 63:
- time = 1885333
- flags = 1
- data = length 191, hash FBDD8162
- sample 64:
- time = 1906666
- flags = 1
- data = length 183, hash 7290332F
- sample 65:
- time = 1928000
- flags = 1
- data = length 189, hash 1A9DC3DE
- sample 66:
- time = 1949333
- flags = 1
- data = length 201, hash 5D936764
- sample 67:
- time = 1970666
- flags = 1
- data = length 193, hash 6B03E75E
- sample 68:
- time = 1992000
- flags = 1
- data = length 199, hash 8A21BA83
- sample 69:
- time = 2013333
- flags = 1
- data = length 41, hash E6362210
- sample 70:
- time = 2025333
- flags = 1
- data = length 43, hash 36A57B44
- sample 71:
- time = 2028000
- flags = 1
- data = length 43, hash E51797D5
- sample 72:
- time = 2030666
- flags = 1
- data = length 43, hash 1F336C72
- sample 73:
- time = 2033333
- flags = 1
- data = length 42, hash 201AD367
- sample 74:
- time = 2036000
- flags = 1
- data = length 50, hash 606CCD6
- sample 75:
- time = 2038666
- flags = 1
- data = length 56, hash B15EBD7A
- sample 76:
- time = 2041333
- flags = 1
- data = length 212, hash 273B8D22
- sample 77:
- time = 2053333
- flags = 1
- data = length 194, hash 44F9CE1
- sample 78:
- time = 2074666
- flags = 1
- data = length 195, hash EDF9EBA1
- sample 79:
- time = 2096000
- flags = 1
- data = length 194, hash CE9F2D26
- sample 80:
- time = 2117333
- flags = 1
- data = length 192, hash 204F8A23
- sample 81:
- time = 2138666
- flags = 1
- data = length 206, hash DFA57E67
- sample 82:
- time = 2160000
- flags = 1
- data = length 196, hash 3CF084AB
- sample 83:
- time = 2181333
- flags = 1
- data = length 202, hash 2AF75C08
- sample 84:
- time = 2202666
- flags = 1
- data = length 203, hash 748EAF7
- sample 85:
- time = 2224000
- flags = 1
- data = length 205, hash ED82379D
- sample 86:
- time = 2245333
- flags = 1
- data = length 193, hash 61F26F22
- sample 87:
- time = 2266666
- flags = 1
- data = length 189, hash 85EF1D20
- sample 88:
- time = 2288000
- flags = 1
- data = length 187, hash 25E41FBF
- sample 89:
- time = 2309333
- flags = 1
- data = length 199, hash F365808
- sample 90:
- time = 2330666
- flags = 1
- data = length 197, hash 94205329
- sample 91:
- time = 2352000
- flags = 1
- data = length 201, hash FA2B2055
- sample 92:
- time = 2373333
- flags = 1
- data = length 194, hash AF95381F
- sample 93:
- time = 2394666
- flags = 1
- data = length 201, hash 923D3534
- sample 94:
- time = 2416000
- flags = 1
- data = length 198, hash 35F84C2E
- sample 95:
- time = 2437333
- flags = 1
- data = length 204, hash 6642CA40
- sample 96:
- time = 2458666
- flags = 1
- data = length 183, hash 3E2DC6BE
- sample 97:
- time = 2480000
- flags = 1
- data = length 197, hash B1E458CE
- sample 98:
- time = 2501333
- flags = 1
- data = length 193, hash E9218C84
- sample 99:
- time = 2522666
- flags = 1
- data = length 192, hash FEF08D4B
- sample 100:
- time = 2544000
- flags = 1
- data = length 201, hash FC411147
- sample 101:
- time = 2565333
- flags = 1
- data = length 218, hash 86893464
- sample 102:
- time = 2586666
- flags = 1
- data = length 226, hash 31C5320
- sample 103:
- time = 2608000
- flags = 1
- data = length 233, hash 9432BEE5
- sample 104:
- time = 2629333
- flags = 1
- data = length 213, hash B3FCC53E
- sample 105:
- time = 2650666
- flags = 1
- data = length 204, hash D70DD5A2
- sample 106:
- time = 2672000
- flags = 1
- data = length 212, hash A4EF1B69
- sample 107:
- time = 2693333
- flags = 1
- data = length 203, hash 8B0748B5
- sample 108:
- time = 2714666
- flags = 1
- data = length 149, hash E455335B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.2.dump b/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.2.dump
deleted file mode 100644
index 078970b..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.2.dump
+++ /dev/null
@@ -1,229 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=3995]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 112000
- id = null
- containerMimeType = null
- sampleMimeType = audio/vorbis
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash 9A8FF207
- data = length 3832, hash 8A406249
- total output bytes = 8658
- sample count = 49
- sample 0:
- time = 1821333
- flags = 1
- data = length 193, hash FB54FB05
- sample 1:
- time = 1842666
- flags = 1
- data = length 199, hash D99C3106
- sample 2:
- time = 1864000
- flags = 1
- data = length 206, hash 253885B9
- sample 3:
- time = 1885333
- flags = 1
- data = length 191, hash FBDD8162
- sample 4:
- time = 1906666
- flags = 1
- data = length 183, hash 7290332F
- sample 5:
- time = 1928000
- flags = 1
- data = length 189, hash 1A9DC3DE
- sample 6:
- time = 1949333
- flags = 1
- data = length 201, hash 5D936764
- sample 7:
- time = 1970666
- flags = 1
- data = length 193, hash 6B03E75E
- sample 8:
- time = 1992000
- flags = 1
- data = length 199, hash 8A21BA83
- sample 9:
- time = 2013333
- flags = 1
- data = length 41, hash E6362210
- sample 10:
- time = 2025333
- flags = 1
- data = length 43, hash 36A57B44
- sample 11:
- time = 2028000
- flags = 1
- data = length 43, hash E51797D5
- sample 12:
- time = 2030666
- flags = 1
- data = length 43, hash 1F336C72
- sample 13:
- time = 2033333
- flags = 1
- data = length 42, hash 201AD367
- sample 14:
- time = 2036000
- flags = 1
- data = length 50, hash 606CCD6
- sample 15:
- time = 2038666
- flags = 1
- data = length 56, hash B15EBD7A
- sample 16:
- time = 2041333
- flags = 1
- data = length 212, hash 273B8D22
- sample 17:
- time = 2053333
- flags = 1
- data = length 194, hash 44F9CE1
- sample 18:
- time = 2074666
- flags = 1
- data = length 195, hash EDF9EBA1
- sample 19:
- time = 2096000
- flags = 1
- data = length 194, hash CE9F2D26
- sample 20:
- time = 2117333
- flags = 1
- data = length 192, hash 204F8A23
- sample 21:
- time = 2138666
- flags = 1
- data = length 206, hash DFA57E67
- sample 22:
- time = 2160000
- flags = 1
- data = length 196, hash 3CF084AB
- sample 23:
- time = 2181333
- flags = 1
- data = length 202, hash 2AF75C08
- sample 24:
- time = 2202666
- flags = 1
- data = length 203, hash 748EAF7
- sample 25:
- time = 2224000
- flags = 1
- data = length 205, hash ED82379D
- sample 26:
- time = 2245333
- flags = 1
- data = length 193, hash 61F26F22
- sample 27:
- time = 2266666
- flags = 1
- data = length 189, hash 85EF1D20
- sample 28:
- time = 2288000
- flags = 1
- data = length 187, hash 25E41FBF
- sample 29:
- time = 2309333
- flags = 1
- data = length 199, hash F365808
- sample 30:
- time = 2330666
- flags = 1
- data = length 197, hash 94205329
- sample 31:
- time = 2352000
- flags = 1
- data = length 201, hash FA2B2055
- sample 32:
- time = 2373333
- flags = 1
- data = length 194, hash AF95381F
- sample 33:
- time = 2394666
- flags = 1
- data = length 201, hash 923D3534
- sample 34:
- time = 2416000
- flags = 1
- data = length 198, hash 35F84C2E
- sample 35:
- time = 2437333
- flags = 1
- data = length 204, hash 6642CA40
- sample 36:
- time = 2458666
- flags = 1
- data = length 183, hash 3E2DC6BE
- sample 37:
- time = 2480000
- flags = 1
- data = length 197, hash B1E458CE
- sample 38:
- time = 2501333
- flags = 1
- data = length 193, hash E9218C84
- sample 39:
- time = 2522666
- flags = 1
- data = length 192, hash FEF08D4B
- sample 40:
- time = 2544000
- flags = 1
- data = length 201, hash FC411147
- sample 41:
- time = 2565333
- flags = 1
- data = length 218, hash 86893464
- sample 42:
- time = 2586666
- flags = 1
- data = length 226, hash 31C5320
- sample 43:
- time = 2608000
- flags = 1
- data = length 233, hash 9432BEE5
- sample 44:
- time = 2629333
- flags = 1
- data = length 213, hash B3FCC53E
- sample 45:
- time = 2650666
- flags = 1
- data = length 204, hash D70DD5A2
- sample 46:
- time = 2672000
- flags = 1
- data = length 212, hash A4EF1B69
- sample 47:
- time = 2693333
- flags = 1
- data = length 203, hash 8B0748B5
- sample 48:
- time = 2714666
- flags = 1
- data = length 149, hash E455335B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.3.dump b/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.3.dump
deleted file mode 100644
index 74ae3cf..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.3.dump
+++ /dev/null
@@ -1,33 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 2741000
- getPosition(0) = [[timeUs=0, position=3995]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 112000
- id = null
- containerMimeType = null
- sampleMimeType = audio/vorbis
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash 9A8FF207
- data = length 3832, hash 8A406249
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.unklen.dump b/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.unklen.dump
deleted file mode 100644
index 0855e96..0000000
--- a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg.unklen.dump
+++ /dev/null
@@ -1,753 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 112000
- id = null
- containerMimeType = null
- sampleMimeType = audio/vorbis
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 30, hash 9A8FF207
- data = length 3832, hash 8A406249
- total output bytes = 26873
- sample count = 180
- sample 0:
- time = 0
- flags = 1
- data = length 49, hash 2FFF94F0
- sample 1:
- time = 0
- flags = 1
- data = length 44, hash 3946418A
- sample 2:
- time = 2666
- flags = 1
- data = length 55, hash 2A0B878E
- sample 3:
- time = 5333
- flags = 1
- data = length 53, hash CC3B6879
- sample 4:
- time = 8000
- flags = 1
- data = length 215, hash 106AE950
- sample 5:
- time = 20000
- flags = 1
- data = length 192, hash 2B219F53
- sample 6:
- time = 41333
- flags = 1
- data = length 197, hash FBC39422
- sample 7:
- time = 62666
- flags = 1
- data = length 209, hash 386E8979
- sample 8:
- time = 84000
- flags = 1
- data = length 42, hash E81162C1
- sample 9:
- time = 96000
- flags = 1
- data = length 41, hash F15BEE36
- sample 10:
- time = 98666
- flags = 1
- data = length 42, hash D67EB19
- sample 11:
- time = 101333
- flags = 1
- data = length 42, hash F4DE4792
- sample 12:
- time = 104000
- flags = 1
- data = length 53, hash 80F66AC3
- sample 13:
- time = 106666
- flags = 1
- data = length 56, hash DCB9DFC4
- sample 14:
- time = 109333
- flags = 1
- data = length 55, hash 4E0C4E9D
- sample 15:
- time = 112000
- flags = 1
- data = length 203, hash 176B6862
- sample 16:
- time = 124000
- flags = 1
- data = length 193, hash AB13CB10
- sample 17:
- time = 145333
- flags = 1
- data = length 203, hash DE63DE9F
- sample 18:
- time = 166666
- flags = 1
- data = length 194, hash 4A9508A2
- sample 19:
- time = 188000
- flags = 1
- data = length 210, hash 196899B3
- sample 20:
- time = 209333
- flags = 1
- data = length 195, hash B68407F1
- sample 21:
- time = 230666
- flags = 1
- data = length 193, hash A1FA86E3
- sample 22:
- time = 252000
- flags = 1
- data = length 194, hash 5C0B9343
- sample 23:
- time = 273333
- flags = 1
- data = length 198, hash 789914B2
- sample 24:
- time = 294666
- flags = 1
- data = length 183, hash 1B82D11F
- sample 25:
- time = 316000
- flags = 1
- data = length 199, hash D5B848F4
- sample 26:
- time = 337333
- flags = 1
- data = length 192, hash B34427EA
- sample 27:
- time = 358666
- flags = 1
- data = length 199, hash C2599BB5
- sample 28:
- time = 380000
- flags = 1
- data = length 195, hash BFD83194
- sample 29:
- time = 401333
- flags = 1
- data = length 199, hash C9A7F7CA
- sample 30:
- time = 422666
- flags = 1
- data = length 44, hash 5D76EAD6
- sample 31:
- time = 434666
- flags = 1
- data = length 43, hash 8619C423
- sample 32:
- time = 437333
- flags = 1
- data = length 43, hash E490BBE
- sample 33:
- time = 440000
- flags = 1
- data = length 53, hash 8A557CAE
- sample 34:
- time = 442666
- flags = 1
- data = length 56, hash 81007BBA
- sample 35:
- time = 445333
- flags = 1
- data = length 56, hash 4E4DD67F
- sample 36:
- time = 448000
- flags = 1
- data = length 222, hash 414188AB
- sample 37:
- time = 460000
- flags = 1
- data = length 202, hash 67A07D30
- sample 38:
- time = 481333
- flags = 1
- data = length 200, hash E357D853
- sample 39:
- time = 502666
- flags = 1
- data = length 203, hash 4653DC90
- sample 40:
- time = 524000
- flags = 1
- data = length 192, hash A65E6C09
- sample 41:
- time = 545333
- flags = 1
- data = length 202, hash FBEAC508
- sample 42:
- time = 566666
- flags = 1
- data = length 202, hash E9B7B59F
- sample 43:
- time = 588000
- flags = 1
- data = length 204, hash E24AA78E
- sample 44:
- time = 609333
- flags = 1
- data = length 41, hash 3FBC5216
- sample 45:
- time = 621333
- flags = 1
- data = length 47, hash 153FBC55
- sample 46:
- time = 624000
- flags = 1
- data = length 42, hash 2B493D6C
- sample 47:
- time = 626666
- flags = 1
- data = length 42, hash 8303BEE3
- sample 48:
- time = 629333
- flags = 1
- data = length 62, hash 71AEE50B
- sample 49:
- time = 632000
- flags = 1
- data = length 54, hash 52F61908
- sample 50:
- time = 634666
- flags = 1
- data = length 45, hash 7BD3E3A1
- sample 51:
- time = 637333
- flags = 1
- data = length 41, hash E0F65472
- sample 52:
- time = 640000
- flags = 1
- data = length 45, hash 41838675
- sample 53:
- time = 642666
- flags = 1
- data = length 44, hash FCBC2147
- sample 54:
- time = 645333
- flags = 1
- data = length 45, hash 1A5987E3
- sample 55:
- time = 648000
- flags = 1
- data = length 43, hash 99074864
- sample 56:
- time = 650666
- flags = 1
- data = length 57, hash D4A9B60A
- sample 57:
- time = 653333
- flags = 1
- data = length 52, hash 302129DA
- sample 58:
- time = 656000
- flags = 1
- data = length 57, hash D8DD99C0
- sample 59:
- time = 658666
- flags = 1
- data = length 206, hash F4B9EF26
- sample 60:
- time = 670666
- flags = 1
- data = length 197, hash 7B8ACC8A
- sample 61:
- time = 692000
- flags = 1
- data = length 186, hash 161027CB
- sample 62:
- time = 713333
- flags = 1
- data = length 186, hash 1D6871B6
- sample 63:
- time = 734666
- flags = 1
- data = length 201, hash 536E9FDB
- sample 64:
- time = 756000
- flags = 1
- data = length 192, hash D38EFAC5
- sample 65:
- time = 777333
- flags = 1
- data = length 194, hash 4B394EF3
- sample 66:
- time = 798666
- flags = 1
- data = length 206, hash 1B31BA99
- sample 67:
- time = 820000
- flags = 1
- data = length 212, hash AD061F43
- sample 68:
- time = 841333
- flags = 1
- data = length 180, hash 6D1F7481
- sample 69:
- time = 862666
- flags = 1
- data = length 195, hash D80B21F
- sample 70:
- time = 884000
- flags = 1
- data = length 186, hash D367882
- sample 71:
- time = 905333
- flags = 1
- data = length 195, hash 2722159A
- sample 72:
- time = 926666
- flags = 1
- data = length 199, hash 10CEE97A
- sample 73:
- time = 948000
- flags = 1
- data = length 191, hash 2CF9FB3F
- sample 74:
- time = 969333
- flags = 1
- data = length 197, hash A725DA0
- sample 75:
- time = 990666
- flags = 1
- data = length 211, hash D4E5DB9E
- sample 76:
- time = 1012000
- flags = 1
- data = length 189, hash 1A90F496
- sample 77:
- time = 1033333
- flags = 1
- data = length 187, hash 44DB2689
- sample 78:
- time = 1054666
- flags = 1
- data = length 197, hash 6D3E5117
- sample 79:
- time = 1076000
- flags = 1
- data = length 208, hash 5B57B288
- sample 80:
- time = 1097333
- flags = 1
- data = length 198, hash D5FC05
- sample 81:
- time = 1118666
- flags = 1
- data = length 192, hash 350BBA45
- sample 82:
- time = 1140000
- flags = 1
- data = length 195, hash 5F96F2A8
- sample 83:
- time = 1161333
- flags = 1
- data = length 202, hash 61D7CC33
- sample 84:
- time = 1182666
- flags = 1
- data = length 202, hash 49D335F2
- sample 85:
- time = 1204000
- flags = 1
- data = length 192, hash 2FE9CB1A
- sample 86:
- time = 1225333
- flags = 1
- data = length 201, hash BF0763B2
- sample 87:
- time = 1246666
- flags = 1
- data = length 184, hash AD047421
- sample 88:
- time = 1268000
- flags = 1
- data = length 196, hash F9088F14
- sample 89:
- time = 1289333
- flags = 1
- data = length 190, hash AC6D38FD
- sample 90:
- time = 1310666
- flags = 1
- data = length 195, hash 8D1A66D2
- sample 91:
- time = 1332000
- flags = 1
- data = length 197, hash B46BFB6B
- sample 92:
- time = 1353333
- flags = 1
- data = length 195, hash D9761F23
- sample 93:
- time = 1374666
- flags = 1
- data = length 204, hash 3391B617
- sample 94:
- time = 1396000
- flags = 1
- data = length 42, hash 33A1FB52
- sample 95:
- time = 1408000
- flags = 1
- data = length 44, hash 408B146E
- sample 96:
- time = 1410666
- flags = 1
- data = length 44, hash 171C7E0D
- sample 97:
- time = 1413333
- flags = 1
- data = length 54, hash 6307E16C
- sample 98:
- time = 1416000
- flags = 1
- data = length 53, hash 4A319572
- sample 99:
- time = 1418666
- flags = 1
- data = length 215, hash BA9C445C
- sample 100:
- time = 1430666
- flags = 1
- data = length 201, hash 3120D234
- sample 101:
- time = 1452000
- flags = 1
- data = length 187, hash DB44993C
- sample 102:
- time = 1473333
- flags = 1
- data = length 196, hash CF2002D7
- sample 103:
- time = 1494666
- flags = 1
- data = length 185, hash E03B5D7
- sample 104:
- time = 1516000
- flags = 1
- data = length 187, hash DA399A2C
- sample 105:
- time = 1537333
- flags = 1
- data = length 191, hash 292AA0DB
- sample 106:
- time = 1558666
- flags = 1
- data = length 201, hash 221910E0
- sample 107:
- time = 1580000
- flags = 1
- data = length 194, hash F4ED7821
- sample 108:
- time = 1601333
- flags = 1
- data = length 43, hash FDDA515E
- sample 109:
- time = 1613333
- flags = 1
- data = length 42, hash F3571C0A
- sample 110:
- time = 1616000
- flags = 1
- data = length 38, hash 39F910B3
- sample 111:
- time = 1618666
- flags = 1
- data = length 41, hash 2D189531
- sample 112:
- time = 1621333
- flags = 1
- data = length 43, hash 1F7574DB
- sample 113:
- time = 1624000
- flags = 1
- data = length 43, hash 644D15E5
- sample 114:
- time = 1626666
- flags = 1
- data = length 49, hash E8A0878
- sample 115:
- time = 1629333
- flags = 1
- data = length 55, hash DFF2046D
- sample 116:
- time = 1632000
- flags = 1
- data = length 49, hash 9FB8A23
- sample 117:
- time = 1634666
- flags = 1
- data = length 41, hash E3E15E3B
- sample 118:
- time = 1637333
- flags = 1
- data = length 42, hash E5D17A32
- sample 119:
- time = 1640000
- flags = 1
- data = length 42, hash F308B653
- sample 120:
- time = 1642666
- flags = 1
- data = length 55, hash BB750D76
- sample 121:
- time = 1645333
- flags = 1
- data = length 51, hash 96772ABF
- sample 122:
- time = 1648000
- flags = 1
- data = length 197, hash E4524346
- sample 123:
- time = 1660000
- flags = 1
- data = length 188, hash AC3E1BB5
- sample 124:
- time = 1681333
- flags = 1
- data = length 195, hash F56DB8A5
- sample 125:
- time = 1702666
- flags = 1
- data = length 198, hash C8970FF7
- sample 126:
- time = 1724000
- flags = 1
- data = length 202, hash AF425C68
- sample 127:
- time = 1745333
- flags = 1
- data = length 196, hash 4215D839
- sample 128:
- time = 1766666
- flags = 1
- data = length 204, hash DB9BE8E3
- sample 129:
- time = 1788000
- flags = 1
- data = length 206, hash E5B20AB8
- sample 130:
- time = 1809333
- flags = 1
- data = length 209, hash D7F47B95
- sample 131:
- time = 1830666
- flags = 1
- data = length 193, hash FB54FB05
- sample 132:
- time = 1852000
- flags = 1
- data = length 199, hash D99C3106
- sample 133:
- time = 1873333
- flags = 1
- data = length 206, hash 253885B9
- sample 134:
- time = 1894666
- flags = 1
- data = length 191, hash FBDD8162
- sample 135:
- time = 1916000
- flags = 1
- data = length 183, hash 7290332F
- sample 136:
- time = 1937333
- flags = 1
- data = length 189, hash 1A9DC3DE
- sample 137:
- time = 1958666
- flags = 1
- data = length 201, hash 5D936764
- sample 138:
- time = 1980000
- flags = 1
- data = length 193, hash 6B03E75E
- sample 139:
- time = 2001333
- flags = 1
- data = length 199, hash 8A21BA83
- sample 140:
- time = 2022666
- flags = 1
- data = length 41, hash E6362210
- sample 141:
- time = 2034666
- flags = 1
- data = length 43, hash 36A57B44
- sample 142:
- time = 2037333
- flags = 1
- data = length 43, hash E51797D5
- sample 143:
- time = 2040000
- flags = 1
- data = length 43, hash 1F336C72
- sample 144:
- time = 2042666
- flags = 1
- data = length 42, hash 201AD367
- sample 145:
- time = 2045333
- flags = 1
- data = length 50, hash 606CCD6
- sample 146:
- time = 2048000
- flags = 1
- data = length 56, hash B15EBD7A
- sample 147:
- time = 2050666
- flags = 1
- data = length 212, hash 273B8D22
- sample 148:
- time = 2062666
- flags = 1
- data = length 194, hash 44F9CE1
- sample 149:
- time = 2084000
- flags = 1
- data = length 195, hash EDF9EBA1
- sample 150:
- time = 2105333
- flags = 1
- data = length 194, hash CE9F2D26
- sample 151:
- time = 2126666
- flags = 1
- data = length 192, hash 204F8A23
- sample 152:
- time = 2148000
- flags = 1
- data = length 206, hash DFA57E67
- sample 153:
- time = 2169333
- flags = 1
- data = length 196, hash 3CF084AB
- sample 154:
- time = 2190666
- flags = 1
- data = length 202, hash 2AF75C08
- sample 155:
- time = 2212000
- flags = 1
- data = length 203, hash 748EAF7
- sample 156:
- time = 2233333
- flags = 1
- data = length 205, hash ED82379D
- sample 157:
- time = 2254666
- flags = 1
- data = length 193, hash 61F26F22
- sample 158:
- time = 2276000
- flags = 1
- data = length 189, hash 85EF1D20
- sample 159:
- time = 2297333
- flags = 1
- data = length 187, hash 25E41FBF
- sample 160:
- time = 2318666
- flags = 1
- data = length 199, hash F365808
- sample 161:
- time = 2340000
- flags = 1
- data = length 197, hash 94205329
- sample 162:
- time = 2361333
- flags = 1
- data = length 201, hash FA2B2055
- sample 163:
- time = 2382666
- flags = 1
- data = length 194, hash AF95381F
- sample 164:
- time = 2404000
- flags = 1
- data = length 201, hash 923D3534
- sample 165:
- time = 2425333
- flags = 1
- data = length 198, hash 35F84C2E
- sample 166:
- time = 2446666
- flags = 1
- data = length 204, hash 6642CA40
- sample 167:
- time = 2468000
- flags = 1
- data = length 183, hash 3E2DC6BE
- sample 168:
- time = 2489333
- flags = 1
- data = length 197, hash B1E458CE
- sample 169:
- time = 2510666
- flags = 1
- data = length 193, hash E9218C84
- sample 170:
- time = 2532000
- flags = 1
- data = length 192, hash FEF08D4B
- sample 171:
- time = 2553333
- flags = 1
- data = length 201, hash FC411147
- sample 172:
- time = 2574666
- flags = 1
- data = length 218, hash 86893464
- sample 173:
- time = 2596000
- flags = 1
- data = length 226, hash 31C5320
- sample 174:
- time = 2617333
- flags = 1
- data = length 233, hash 9432BEE5
- sample 175:
- time = 2638666
- flags = 1
- data = length 213, hash B3FCC53E
- sample 176:
- time = 2660000
- flags = 1
- data = length 204, hash D70DD5A2
- sample 177:
- time = 2681333
- flags = 1
- data = length 212, hash A4EF1B69
- sample 178:
- time = 2702666
- flags = 1
- data = length 203, hash 8B0748B5
- sample 179:
- time = 2724000
- flags = 1
- data = length 149, hash E455335B
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/rawcc/sample.rawcc.0.dump b/tree/library/extractor/src/test/assets/rawcc/sample.rawcc.0.dump
deleted file mode 100644
index 9aca747..0000000
--- a/tree/library/extractor/src/test/assets/rawcc/sample.rawcc.0.dump
+++ /dev/null
@@ -1,631 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = null
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 978
- sample count = 150
- sample 0:
- time = 37657512133
- flags = 1
- data = length 3, hash 7363
- sample 1:
- time = 37657528822
- flags = 1
- data = length 3, hash 7724
- sample 2:
- time = 37657545511
- flags = 1
- data = length 3, hash 766F
- sample 3:
- time = 37657562177
- flags = 1
- data = length 3, hash 7724
- sample 4:
- time = 37657578866
- flags = 1
- data = length 3, hash 767E
- sample 5:
- time = 37657595555
- flags = 1
- data = length 3, hash 7724
- sample 6:
- time = 37657612244
- flags = 1
- data = length 15, hash E4359178
- sample 7:
- time = 37657628911
- flags = 1
- data = length 3, hash 7724
- sample 8:
- time = 37657645600
- flags = 1
- data = length 12, hash 15EBEB66
- sample 9:
- time = 37657662288
- flags = 1
- data = length 3, hash 7724
- sample 10:
- time = 37657678977
- flags = 1
- data = length 3, hash 761D
- sample 11:
- time = 37657695644
- flags = 1
- data = length 3, hash 7724
- sample 12:
- time = 37657712333
- flags = 1
- data = length 30, hash E181418F
- sample 13:
- time = 37657729022
- flags = 1
- data = length 6, hash 36289CE2
- sample 14:
- time = 37657745711
- flags = 1
- data = length 12, hash 3C304F5B
- sample 15:
- time = 37657762377
- flags = 1
- data = length 3, hash 7724
- sample 16:
- time = 37657779066
- flags = 1
- data = length 12, hash 88DD8EF6
- sample 17:
- time = 37657795755
- flags = 1
- data = length 3, hash 7724
- sample 18:
- time = 37657812444
- flags = 1
- data = length 12, hash 8B411833
- sample 19:
- time = 37657829111
- flags = 1
- data = length 3, hash 7724
- sample 20:
- time = 37657845800
- flags = 1
- data = length 12, hash 742A2DF1
- sample 21:
- time = 37657862488
- flags = 1
- data = length 3, hash 7724
- sample 22:
- time = 37657879177
- flags = 1
- data = length 12, hash 9A2ECBEE
- sample 23:
- time = 37657895844
- flags = 1
- data = length 3, hash 7724
- sample 24:
- time = 37657912533
- flags = 1
- data = length 12, hash 562688EA
- sample 25:
- time = 37657929222
- flags = 1
- data = length 3, hash 7724
- sample 26:
- time = 37657945911
- flags = 1
- data = length 12, hash ADE4B953
- sample 27:
- time = 37657962577
- flags = 1
- data = length 3, hash 7724
- sample 28:
- time = 37657979266
- flags = 1
- data = length 12, hash F927E3E5
- sample 29:
- time = 37657995955
- flags = 1
- data = length 3, hash 7724
- sample 30:
- time = 37658012644
- flags = 1
- data = length 12, hash EA327945
- sample 31:
- time = 37658029311
- flags = 1
- data = length 3, hash 7724
- sample 32:
- time = 37658046000
- flags = 1
- data = length 12, hash 3E5DA13C
- sample 33:
- time = 37658062688
- flags = 1
- data = length 3, hash 7724
- sample 34:
- time = 37658079377
- flags = 1
- data = length 12, hash BF646AE3
- sample 35:
- time = 37658096044
- flags = 1
- data = length 3, hash 7724
- sample 36:
- time = 37658112733
- flags = 1
- data = length 12, hash 41E3BA78
- sample 37:
- time = 37658129422
- flags = 1
- data = length 3, hash 7724
- sample 38:
- time = 37658146111
- flags = 1
- data = length 12, hash A2945EF6
- sample 39:
- time = 37658162777
- flags = 1
- data = length 3, hash 7724
- sample 40:
- time = 37658179466
- flags = 1
- data = length 12, hash 26735812
- sample 41:
- time = 37658196155
- flags = 1
- data = length 3, hash 7724
- sample 42:
- time = 37658212844
- flags = 1
- data = length 12, hash DC14D3D8
- sample 43:
- time = 37658229511
- flags = 1
- data = length 3, hash 7724
- sample 44:
- time = 37658246200
- flags = 1
- data = length 12, hash 882191BE
- sample 45:
- time = 37658262888
- flags = 1
- data = length 3, hash 7724
- sample 46:
- time = 37658279577
- flags = 1
- data = length 12, hash 8B4886B1
- sample 47:
- time = 37658296244
- flags = 1
- data = length 3, hash 7724
- sample 48:
- time = 37658312933
- flags = 1
- data = length 12, hash 98D98F96
- sample 49:
- time = 37658329622
- flags = 1
- data = length 3, hash 7724
- sample 50:
- time = 37658346311
- flags = 1
- data = length 30, hash CF8E53E3
- sample 51:
- time = 37658362977
- flags = 1
- data = length 6, hash 36289CE2
- sample 52:
- time = 37658379666
- flags = 1
- data = length 12, hash F883C9EE
- sample 53:
- time = 37658396355
- flags = 1
- data = length 3, hash 7724
- sample 54:
- time = 37658413044
- flags = 1
- data = length 12, hash 6E6B2B9C
- sample 55:
- time = 37658429711
- flags = 1
- data = length 3, hash 7724
- sample 56:
- time = 37658446400
- flags = 1
- data = length 12, hash B4FE7F08
- sample 57:
- time = 37658463088
- flags = 1
- data = length 3, hash 7724
- sample 58:
- time = 37658479777
- flags = 1
- data = length 12, hash 5A1EA7C7
- sample 59:
- time = 37658496444
- flags = 1
- data = length 3, hash 7724
- sample 60:
- time = 37658513133
- flags = 1
- data = length 12, hash 46BD6CC9
- sample 61:
- time = 37658529822
- flags = 1
- data = length 3, hash 7724
- sample 62:
- time = 37658546511
- flags = 1
- data = length 12, hash 1B1E2554
- sample 63:
- time = 37658563177
- flags = 1
- data = length 3, hash 7724
- sample 64:
- time = 37658579866
- flags = 1
- data = length 12, hash 91FCC537
- sample 65:
- time = 37658596555
- flags = 1
- data = length 3, hash 7724
- sample 66:
- time = 37658613244
- flags = 1
- data = length 12, hash A9355E1B
- sample 67:
- time = 37658629911
- flags = 1
- data = length 3, hash 7724
- sample 68:
- time = 37658646600
- flags = 1
- data = length 12, hash 2511F69B
- sample 69:
- time = 37658663288
- flags = 1
- data = length 3, hash 7724
- sample 70:
- time = 37658679977
- flags = 1
- data = length 12, hash 90925736
- sample 71:
- time = 37658696644
- flags = 1
- data = length 3, hash 7724
- sample 72:
- time = 37658713333
- flags = 1
- data = length 21, hash 431EEE30
- sample 73:
- time = 37658730022
- flags = 1
- data = length 3, hash 7724
- sample 74:
- time = 37658746711
- flags = 1
- data = length 12, hash 7BDEF631
- sample 75:
- time = 37658763377
- flags = 1
- data = length 3, hash 7724
- sample 76:
- time = 37658780066
- flags = 1
- data = length 12, hash A2EEF59E
- sample 77:
- time = 37658796755
- flags = 1
- data = length 3, hash 7724
- sample 78:
- time = 37658813444
- flags = 1
- data = length 12, hash BFC6C022
- sample 79:
- time = 37658830111
- flags = 1
- data = length 3, hash 7724
- sample 80:
- time = 37658846800
- flags = 1
- data = length 12, hash CD4D8FCA
- sample 81:
- time = 37658863488
- flags = 1
- data = length 3, hash 7724
- sample 82:
- time = 37658880177
- flags = 1
- data = length 12, hash 2BDE8EFA
- sample 83:
- time = 37658896844
- flags = 1
- data = length 3, hash 7724
- sample 84:
- time = 37658913533
- flags = 1
- data = length 12, hash 8C858812
- sample 85:
- time = 37658930222
- flags = 1
- data = length 3, hash 7724
- sample 86:
- time = 37658946911
- flags = 1
- data = length 12, hash DE7D0E31
- sample 87:
- time = 37658963577
- flags = 1
- data = length 3, hash 7724
- sample 88:
- time = 37658980266
- flags = 1
- data = length 3, hash 7363
- sample 89:
- time = 37658996955
- flags = 1
- data = length 3, hash 7724
- sample 90:
- time = 37659013644
- flags = 1
- data = length 3, hash 7363
- sample 91:
- time = 37659030311
- flags = 1
- data = length 3, hash 7724
- sample 92:
- time = 37659047000
- flags = 1
- data = length 3, hash 7363
- sample 93:
- time = 37659063688
- flags = 1
- data = length 3, hash 7724
- sample 94:
- time = 37659080377
- flags = 1
- data = length 3, hash 7363
- sample 95:
- time = 37659097044
- flags = 1
- data = length 3, hash 7724
- sample 96:
- time = 37659113733
- flags = 1
- data = length 3, hash 7363
- sample 97:
- time = 37659130422
- flags = 1
- data = length 3, hash 7724
- sample 98:
- time = 37659147111
- flags = 1
- data = length 3, hash 7363
- sample 99:
- time = 37659163777
- flags = 1
- data = length 3, hash 7724
- sample 100:
- time = 37659180466
- flags = 1
- data = length 3, hash 7363
- sample 101:
- time = 37659197155
- flags = 1
- data = length 3, hash 7724
- sample 102:
- time = 37659213844
- flags = 1
- data = length 3, hash 7363
- sample 103:
- time = 37659230511
- flags = 1
- data = length 3, hash 7724
- sample 104:
- time = 37659247200
- flags = 1
- data = length 3, hash 7363
- sample 105:
- time = 37659263888
- flags = 1
- data = length 3, hash 7724
- sample 106:
- time = 37659280577
- flags = 1
- data = length 3, hash 7363
- sample 107:
- time = 37659297244
- flags = 1
- data = length 3, hash 7724
- sample 108:
- time = 37659313933
- flags = 1
- data = length 3, hash 7363
- sample 109:
- time = 37659330622
- flags = 1
- data = length 3, hash 7724
- sample 110:
- time = 37659347311
- flags = 1
- data = length 3, hash 7363
- sample 111:
- time = 37659363977
- flags = 1
- data = length 3, hash 7724
- sample 112:
- time = 37659380666
- flags = 1
- data = length 3, hash 7363
- sample 113:
- time = 37659397355
- flags = 1
- data = length 3, hash 7724
- sample 114:
- time = 37659414044
- flags = 1
- data = length 3, hash 7363
- sample 115:
- time = 37659430711
- flags = 1
- data = length 3, hash 7724
- sample 116:
- time = 37659447400
- flags = 1
- data = length 3, hash 7363
- sample 117:
- time = 37659464088
- flags = 1
- data = length 3, hash 7724
- sample 118:
- time = 37659480777
- flags = 1
- data = length 3, hash 7363
- sample 119:
- time = 37659497444
- flags = 1
- data = length 3, hash 7724
- sample 120:
- time = 37659514133
- flags = 1
- data = length 3, hash 7363
- sample 121:
- time = 37659530822
- flags = 1
- data = length 3, hash 7724
- sample 122:
- time = 37659547511
- flags = 1
- data = length 3, hash 7363
- sample 123:
- time = 37659564177
- flags = 1
- data = length 3, hash 7724
- sample 124:
- time = 37659580866
- flags = 1
- data = length 3, hash 7363
- sample 125:
- time = 37659597555
- flags = 1
- data = length 3, hash 7724
- sample 126:
- time = 37659614244
- flags = 1
- data = length 3, hash 766F
- sample 127:
- time = 37659630911
- flags = 1
- data = length 3, hash 7724
- sample 128:
- time = 37659647600
- flags = 1
- data = length 3, hash 767E
- sample 129:
- time = 37659664288
- flags = 1
- data = length 3, hash 7724
- sample 130:
- time = 37659680977
- flags = 1
- data = length 15, hash 191B585A
- sample 131:
- time = 37659697644
- flags = 1
- data = length 3, hash 7724
- sample 132:
- time = 37659714333
- flags = 1
- data = length 12, hash 15EC5FC5
- sample 133:
- time = 37659731022
- flags = 1
- data = length 3, hash 7724
- sample 134:
- time = 37659747711
- flags = 1
- data = length 3, hash 76A1
- sample 135:
- time = 37659764377
- flags = 1
- data = length 3, hash 7724
- sample 136:
- time = 37659781066
- flags = 1
- data = length 30, hash E8012479
- sample 137:
- time = 37659797755
- flags = 1
- data = length 6, hash 36289D5E
- sample 138:
- time = 37659814444
- flags = 1
- data = length 12, hash D32F29F3
- sample 139:
- time = 37659831111
- flags = 1
- data = length 3, hash 7724
- sample 140:
- time = 37659847800
- flags = 1
- data = length 21, hash 6258623
- sample 141:
- time = 37659864488
- flags = 1
- data = length 3, hash 7724
- sample 142:
- time = 37659881177
- flags = 1
- data = length 12, hash FE69ABA2
- sample 143:
- time = 37659897844
- flags = 1
- data = length 3, hash 7724
- sample 144:
- time = 37659914533
- flags = 1
- data = length 12, hash 958D0815
- sample 145:
- time = 37659931222
- flags = 1
- data = length 3, hash 7724
- sample 146:
- time = 37659947911
- flags = 1
- data = length 12, hash FF57BFD8
- sample 147:
- time = 37659964577
- flags = 1
- data = length 3, hash 7724
- sample 148:
- time = 37659981266
- flags = 1
- data = length 12, hash 922122E7
- sample 149:
- time = 37659997955
- flags = 1
- data = length 3, hash 7724
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ac3.0.dump b/tree/library/extractor/src/test/assets/ts/sample.ac3.0.dump
deleted file mode 100644
index de37c06..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ac3.0.dump
+++ /dev/null
@@ -1,63 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/ac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 6
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 13281
- sample count = 8
- sample 0:
- time = 0
- flags = 1
- data = length 1536, hash 7108D5C2
- sample 1:
- time = 32000
- flags = 1
- data = length 1536, hash 80BF3B34
- sample 2:
- time = 64000
- flags = 1
- data = length 1536, hash 5D09685
- sample 3:
- time = 96000
- flags = 1
- data = length 1536, hash A9A24E44
- sample 4:
- time = 128000
- flags = 1
- data = length 1536, hash 6F856273
- sample 5:
- time = 160000
- flags = 1
- data = length 1536, hash B1737D3C
- sample 6:
- time = 192000
- flags = 1
- data = length 1536, hash 98FDEB9D
- sample 7:
- time = 224000
- flags = 1
- data = length 1536, hash 99B9B943
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ac4.0.dump b/tree/library/extractor/src/test/assets/ts/sample.ac4.0.dump
deleted file mode 100644
index 9df4b77..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ac4.0.dump
+++ /dev/null
@@ -1,107 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/ac4
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 2
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 7594
- sample count = 19
- sample 0:
- time = 0
- flags = 1
- data = length 366, hash B4277F9E
- sample 1:
- time = 40000
- flags = 1
- data = length 366, hash E8E0A142
- sample 2:
- time = 80000
- flags = 1
- data = length 366, hash 2E5073D0
- sample 3:
- time = 120000
- flags = 1
- data = length 366, hash 850E71D8
- sample 4:
- time = 160000
- flags = 1
- data = length 366, hash 69CD444E
- sample 5:
- time = 200000
- flags = 1
- data = length 366, hash BD24F36D
- sample 6:
- time = 240000
- flags = 1
- data = length 366, hash E24F2490
- sample 7:
- time = 280000
- flags = 1
- data = length 366, hash EE6F1F06
- sample 8:
- time = 320000
- flags = 1
- data = length 366, hash 2DAB000F
- sample 9:
- time = 360000
- flags = 1
- data = length 366, hash 8102B7EC
- sample 10:
- time = 400000
- flags = 1
- data = length 366, hash 55BF59AC
- sample 11:
- time = 440000
- flags = 1
- data = length 494, hash CBC2E09F
- sample 12:
- time = 480000
- flags = 1
- data = length 519, hash 9DAF56E9
- sample 13:
- time = 520000
- flags = 1
- data = length 598, hash 8169EE2
- sample 14:
- time = 560000
- flags = 1
- data = length 435, hash 28C21246
- sample 15:
- time = 600000
- flags = 1
- data = length 365, hash FF14716D
- sample 16:
- time = 640000
- flags = 1
- data = length 392, hash 4CC96B29
- sample 17:
- time = 680000
- flags = 1
- data = length 373, hash D7AC6D4E
- sample 18:
- time = 720000
- flags = 1
- data = length 392, hash 99F2511F
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.adts.0.dump b/tree/library/extractor/src/test/assets/ts/sample.adts.0.dump
deleted file mode 100644
index 9b24043..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.adts.0.dump
+++ /dev/null
@@ -1,633 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 30797
- sample count = 144
- sample 0:
- time = 0
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 23219
- flags = 1
- data = length 6, hash 31CF3A46
- sample 2:
- time = 46438
- flags = 1
- data = length 6, hash 31CF3A46
- sample 3:
- time = 69657
- flags = 1
- data = length 6, hash 31CF3A46
- sample 4:
- time = 92876
- flags = 1
- data = length 6, hash 31EC5206
- sample 5:
- time = 116095
- flags = 1
- data = length 171, hash 4F6478F6
- sample 6:
- time = 139314
- flags = 1
- data = length 202, hash AF4068A3
- sample 7:
- time = 162533
- flags = 1
- data = length 210, hash E4C10618
- sample 8:
- time = 185752
- flags = 1
- data = length 217, hash 9ECCD0D9
- sample 9:
- time = 208971
- flags = 1
- data = length 212, hash 6BAC2CD9
- sample 10:
- time = 232190
- flags = 1
- data = length 223, hash 188B6010
- sample 11:
- time = 255409
- flags = 1
- data = length 222, hash C1A04D0C
- sample 12:
- time = 278628
- flags = 1
- data = length 220, hash D65F9768
- sample 13:
- time = 301847
- flags = 1
- data = length 227, hash B96C9E14
- sample 14:
- time = 325066
- flags = 1
- data = length 229, hash 9FB09972
- sample 15:
- time = 348285
- flags = 1
- data = length 220, hash 2271F053
- sample 16:
- time = 371504
- flags = 1
- data = length 226, hash 5EDD2F4F
- sample 17:
- time = 394723
- flags = 1
- data = length 239, hash 957510E0
- sample 18:
- time = 417942
- flags = 1
- data = length 224, hash 718A8F47
- sample 19:
- time = 441161
- flags = 1
- data = length 225, hash 5E11E293
- sample 20:
- time = 464380
- flags = 1
- data = length 227, hash FCE50D27
- sample 21:
- time = 487599
- flags = 1
- data = length 212, hash 77908C40
- sample 22:
- time = 510818
- flags = 1
- data = length 227, hash 34C4EB32
- sample 23:
- time = 534037
- flags = 1
- data = length 231, hash 95488307
- sample 24:
- time = 557256
- flags = 1
- data = length 226, hash 97F12D6F
- sample 25:
- time = 580475
- flags = 1
- data = length 236, hash 91A9D9A2
- sample 26:
- time = 603694
- flags = 1
- data = length 227, hash 27A608F9
- sample 27:
- time = 626913
- flags = 1
- data = length 229, hash 57DAAE4
- sample 28:
- time = 650132
- flags = 1
- data = length 235, hash ED30AC34
- sample 29:
- time = 673351
- flags = 1
- data = length 227, hash BD3D6280
- sample 30:
- time = 696570
- flags = 1
- data = length 233, hash 694B1087
- sample 31:
- time = 719789
- flags = 1
- data = length 232, hash 1EDFE047
- sample 32:
- time = 743008
- flags = 1
- data = length 228, hash E2A831F4
- sample 33:
- time = 766227
- flags = 1
- data = length 231, hash 757E6012
- sample 34:
- time = 789446
- flags = 1
- data = length 223, hash 4003D791
- sample 35:
- time = 812665
- flags = 1
- data = length 232, hash 3CF9A07C
- sample 36:
- time = 835884
- flags = 1
- data = length 228, hash 25AC3FF7
- sample 37:
- time = 859103
- flags = 1
- data = length 220, hash 2C1824CE
- sample 38:
- time = 882322
- flags = 1
- data = length 229, hash 46FDD8FB
- sample 39:
- time = 905541
- flags = 1
- data = length 237, hash F6988018
- sample 40:
- time = 928760
- flags = 1
- data = length 242, hash 60436B6B
- sample 41:
- time = 951979
- flags = 1
- data = length 275, hash 90EDFA8E
- sample 42:
- time = 975198
- flags = 1
- data = length 242, hash 5C86EFCB
- sample 43:
- time = 998417
- flags = 1
- data = length 233, hash E0A51B82
- sample 44:
- time = 1021636
- flags = 1
- data = length 235, hash 590DF14F
- sample 45:
- time = 1044855
- flags = 1
- data = length 238, hash 69AF4E6E
- sample 46:
- time = 1068074
- flags = 1
- data = length 235, hash E745AE8D
- sample 47:
- time = 1091293
- flags = 1
- data = length 223, hash 295F2A13
- sample 48:
- time = 1114512
- flags = 1
- data = length 228, hash E2F47B21
- sample 49:
- time = 1137731
- flags = 1
- data = length 229, hash 262C3CFE
- sample 50:
- time = 1160950
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 51:
- time = 1184169
- flags = 1
- data = length 233, hash F3D80836
- sample 52:
- time = 1207388
- flags = 1
- data = length 237, hash 32E0A11E
- sample 53:
- time = 1230607
- flags = 1
- data = length 228, hash E1B89F13
- sample 54:
- time = 1253826
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 55:
- time = 1277045
- flags = 1
- data = length 235, hash 3C84161F
- sample 56:
- time = 1300264
- flags = 1
- data = length 227, hash A47E1789
- sample 57:
- time = 1323483
- flags = 1
- data = length 228, hash 869FDFD3
- sample 58:
- time = 1346702
- flags = 1
- data = length 233, hash 272ECE2
- sample 59:
- time = 1369921
- flags = 1
- data = length 227, hash DB6B9618
- sample 60:
- time = 1393140
- flags = 1
- data = length 212, hash 63214325
- sample 61:
- time = 1416359
- flags = 1
- data = length 221, hash 9BA588A1
- sample 62:
- time = 1439578
- flags = 1
- data = length 225, hash 21EFD50C
- sample 63:
- time = 1462797
- flags = 1
- data = length 231, hash F3AD0BF
- sample 64:
- time = 1486016
- flags = 1
- data = length 224, hash 822C9210
- sample 65:
- time = 1509235
- flags = 1
- data = length 195, hash D4EF53EE
- sample 66:
- time = 1532454
- flags = 1
- data = length 195, hash A816647A
- sample 67:
- time = 1555673
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 68:
- time = 1578892
- flags = 1
- data = length 210, hash 956E3600
- sample 69:
- time = 1602111
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 70:
- time = 1625330
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 71:
- time = 1648549
- flags = 1
- data = length 228, hash F3B70641
- sample 72:
- time = 1671768
- flags = 1
- data = length 237, hash 124E3194
- sample 73:
- time = 1694987
- flags = 1
- data = length 231, hash 950CD7C8
- sample 74:
- time = 1718206
- flags = 1
- data = length 236, hash A12E49AF
- sample 75:
- time = 1741425
- flags = 1
- data = length 242, hash 43BC9C24
- sample 76:
- time = 1764644
- flags = 1
- data = length 241, hash DCF0B17
- sample 77:
- time = 1787863
- flags = 1
- data = length 251, hash C0B99968
- sample 78:
- time = 1811082
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 79:
- time = 1834301
- flags = 1
- data = length 238, hash 1BA69079
- sample 80:
- time = 1857520
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 81:
- time = 1880739
- flags = 1
- data = length 231, hash EABBEE02
- sample 82:
- time = 1903958
- flags = 1
- data = length 226, hash D09C44FB
- sample 83:
- time = 1927177
- flags = 1
- data = length 235, hash BE6A6608
- sample 84:
- time = 1950396
- flags = 1
- data = length 235, hash 2735F454
- sample 85:
- time = 1973615
- flags = 1
- data = length 238, hash B160DFE7
- sample 86:
- time = 1996834
- flags = 1
- data = length 232, hash 1B217D2E
- sample 87:
- time = 2020053
- flags = 1
- data = length 251, hash D1C14CEA
- sample 88:
- time = 2043272
- flags = 1
- data = length 256, hash 97C87F08
- sample 89:
- time = 2066491
- flags = 1
- data = length 237, hash 6645DB3
- sample 90:
- time = 2089710
- flags = 1
- data = length 235, hash 727A1C82
- sample 91:
- time = 2112929
- flags = 1
- data = length 234, hash 5015F8B5
- sample 92:
- time = 2136148
- flags = 1
- data = length 241, hash 9102144B
- sample 93:
- time = 2159367
- flags = 1
- data = length 224, hash 64E0D807
- sample 94:
- time = 2182586
- flags = 1
- data = length 228, hash 1922B852
- sample 95:
- time = 2205805
- flags = 1
- data = length 224, hash 953502D8
- sample 96:
- time = 2229024
- flags = 1
- data = length 214, hash 92B87FE7
- sample 97:
- time = 2252243
- flags = 1
- data = length 213, hash BB0C8D86
- sample 98:
- time = 2275462
- flags = 1
- data = length 206, hash 9AD21017
- sample 99:
- time = 2298681
- flags = 1
- data = length 209, hash C479FE94
- sample 100:
- time = 2321900
- flags = 1
- data = length 220, hash 3033DCE1
- sample 101:
- time = 2345119
- flags = 1
- data = length 217, hash 7D589C94
- sample 102:
- time = 2368338
- flags = 1
- data = length 216, hash AAF6C183
- sample 103:
- time = 2391557
- flags = 1
- data = length 206, hash 1EE1207F
- sample 104:
- time = 2414776
- flags = 1
- data = length 204, hash 4BEB1210
- sample 105:
- time = 2437995
- flags = 1
- data = length 213, hash 21A841C9
- sample 106:
- time = 2461214
- flags = 1
- data = length 207, hash B80B0424
- sample 107:
- time = 2484433
- flags = 1
- data = length 212, hash 4785A1C3
- sample 108:
- time = 2507652
- flags = 1
- data = length 205, hash 59BF7229
- sample 109:
- time = 2530871
- flags = 1
- data = length 208, hash FA313DDE
- sample 110:
- time = 2554090
- flags = 1
- data = length 211, hash 190D85FD
- sample 111:
- time = 2577309
- flags = 1
- data = length 211, hash BA050052
- sample 112:
- time = 2600528
- flags = 1
- data = length 211, hash F3080F10
- sample 113:
- time = 2623747
- flags = 1
- data = length 210, hash F41B7BE7
- sample 114:
- time = 2646966
- flags = 1
- data = length 207, hash 2176C97E
- sample 115:
- time = 2670185
- flags = 1
- data = length 220, hash 32087455
- sample 116:
- time = 2693404
- flags = 1
- data = length 213, hash 4E5649A8
- sample 117:
- time = 2716623
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 118:
- time = 2739842
- flags = 1
- data = length 204, hash 1E895C2A
- sample 119:
- time = 2763061
- flags = 1
- data = length 219, hash 45382270
- sample 120:
- time = 2786280
- flags = 1
- data = length 205, hash D66C6A1D
- sample 121:
- time = 2809499
- flags = 1
- data = length 204, hash 467AD01F
- sample 122:
- time = 2832718
- flags = 1
- data = length 211, hash F0435574
- sample 123:
- time = 2855937
- flags = 1
- data = length 206, hash 8C96B75F
- sample 124:
- time = 2879156
- flags = 1
- data = length 200, hash 82553248
- sample 125:
- time = 2902375
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 126:
- time = 2925594
- flags = 1
- data = length 196, hash 33151DC4
- sample 127:
- time = 2948813
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 128:
- time = 2972032
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 129:
- time = 2995251
- flags = 1
- data = length 209, hash A72FABAA
- sample 130:
- time = 3018470
- flags = 1
- data = length 217, hash BA33B985
- sample 131:
- time = 3041689
- flags = 1
- data = length 235, hash 9919CFD9
- sample 132:
- time = 3064908
- flags = 1
- data = length 236, hash A22C7267
- sample 133:
- time = 3088127
- flags = 1
- data = length 213, hash 3D57C901
- sample 134:
- time = 3111346
- flags = 1
- data = length 205, hash 47F68FDE
- sample 135:
- time = 3134565
- flags = 1
- data = length 210, hash 9A756E9C
- sample 136:
- time = 3157784
- flags = 1
- data = length 210, hash BD45C31F
- sample 137:
- time = 3181003
- flags = 1
- data = length 207, hash 8774FF7B
- sample 138:
- time = 3204222
- flags = 1
- data = length 149, hash 4678C0E5
- sample 139:
- time = 3227441
- flags = 1
- data = length 161, hash E991035D
- sample 140:
- time = 3250660
- flags = 1
- data = length 197, hash C3013689
- sample 141:
- time = 3273879
- flags = 1
- data = length 208, hash E6C0237
- sample 142:
- time = 3297098
- flags = 1
- data = length 232, hash A330F188
- sample 143:
- time = 3320317
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.eac3.0.dump b/tree/library/extractor/src/test/assets/ts/sample.eac3.0.dump
deleted file mode 100644
index 5841500..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.eac3.0.dump
+++ /dev/null
@@ -1,247 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/eac3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 6
- sampleRate = 48000
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 216000
- sample count = 54
- sample 0:
- time = 0
- flags = 1
- data = length 4000, hash BAEAFB2A
- sample 1:
- time = 5333
- flags = 1
- data = length 4000, hash E3C5EBF0
- sample 2:
- time = 10666
- flags = 1
- data = length 4000, hash 32E0F957
- sample 3:
- time = 15999
- flags = 1
- data = length 4000, hash 5354CC5D
- sample 4:
- time = 21332
- flags = 1
- data = length 4000, hash FF834906
- sample 5:
- time = 26665
- flags = 1
- data = length 4000, hash 6F571E61
- sample 6:
- time = 31998
- flags = 1
- data = length 4000, hash 5C931F6B
- sample 7:
- time = 37331
- flags = 1
- data = length 4000, hash B1FB2E57
- sample 8:
- time = 42664
- flags = 1
- data = length 4000, hash C71240EB
- sample 9:
- time = 47997
- flags = 1
- data = length 4000, hash C3E302EE
- sample 10:
- time = 53330
- flags = 1
- data = length 4000, hash 7994C27B
- sample 11:
- time = 58663
- flags = 1
- data = length 4000, hash 1ED4E6F3
- sample 12:
- time = 63996
- flags = 1
- data = length 4000, hash 1D5E6AAC
- sample 13:
- time = 69329
- flags = 1
- data = length 4000, hash 30058F51
- sample 14:
- time = 74662
- flags = 1
- data = length 4000, hash 15DD0E4A
- sample 15:
- time = 79995
- flags = 1
- data = length 4000, hash 37BE7C15
- sample 16:
- time = 85328
- flags = 1
- data = length 4000, hash 7CFDD34B
- sample 17:
- time = 90661
- flags = 1
- data = length 4000, hash 27F20D29
- sample 18:
- time = 95994
- flags = 1
- data = length 4000, hash 6F565894
- sample 19:
- time = 101327
- flags = 1
- data = length 4000, hash A6F07C4A
- sample 20:
- time = 106660
- flags = 1
- data = length 4000, hash 3A0CA15C
- sample 21:
- time = 111993
- flags = 1
- data = length 4000, hash DB365414
- sample 22:
- time = 117326
- flags = 1
- data = length 4000, hash 31E08469
- sample 23:
- time = 122659
- flags = 1
- data = length 4000, hash 315F5C28
- sample 24:
- time = 127992
- flags = 1
- data = length 4000, hash CC65DF80
- sample 25:
- time = 133325
- flags = 1
- data = length 4000, hash 503FB64C
- sample 26:
- time = 138658
- flags = 1
- data = length 4000, hash 817CF735
- sample 27:
- time = 143991
- flags = 1
- data = length 4000, hash 37391ADA
- sample 28:
- time = 149324
- flags = 1
- data = length 4000, hash 37391ADA
- sample 29:
- time = 154657
- flags = 1
- data = length 4000, hash 64DBF751
- sample 30:
- time = 159990
- flags = 1
- data = length 4000, hash 81AE828E
- sample 31:
- time = 165323
- flags = 1
- data = length 4000, hash 767D6C98
- sample 32:
- time = 170656
- flags = 1
- data = length 4000, hash A5F6D4E
- sample 33:
- time = 175989
- flags = 1
- data = length 4000, hash EABC6B0D
- sample 34:
- time = 181322
- flags = 1
- data = length 4000, hash F47EF742
- sample 35:
- time = 186655
- flags = 1
- data = length 4000, hash 9B2549DA
- sample 36:
- time = 191988
- flags = 1
- data = length 4000, hash A12733C9
- sample 37:
- time = 197321
- flags = 1
- data = length 4000, hash 95F62E99
- sample 38:
- time = 202654
- flags = 1
- data = length 4000, hash A4D858
- sample 39:
- time = 207987
- flags = 1
- data = length 4000, hash A4D858
- sample 40:
- time = 213320
- flags = 1
- data = length 4000, hash 22C1A129
- sample 41:
- time = 218653
- flags = 1
- data = length 4000, hash 2C51E4A1
- sample 42:
- time = 223986
- flags = 1
- data = length 4000, hash 3782E8BB
- sample 43:
- time = 229319
- flags = 1
- data = length 4000, hash 2C51E4A1
- sample 44:
- time = 234652
- flags = 1
- data = length 4000, hash BDB3D129
- sample 45:
- time = 239985
- flags = 1
- data = length 4000, hash F642A55
- sample 46:
- time = 245318
- flags = 1
- data = length 4000, hash 32F259F4
- sample 47:
- time = 250651
- flags = 1
- data = length 4000, hash 4C987B7C
- sample 48:
- time = 255984
- flags = 1
- data = length 4000, hash 57C98E1C
- sample 49:
- time = 261317
- flags = 1
- data = length 4000, hash 4C987B7C
- sample 50:
- time = 266650
- flags = 1
- data = length 4000, hash 4C987B7C
- sample 51:
- time = 271983
- flags = 1
- data = length 4000, hash 4C987B7C
- sample 52:
- time = 277316
- flags = 1
- data = length 4000, hash 4C987B7C
- sample 53:
- time = 282649
- flags = 1
- data = length 4000, hash 4C987B7C
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps.0.dump b/tree/library/extractor/src/test/assets/ts/sample.ps.0.dump
deleted file mode 100644
index 09b3281..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ps.0.dump
+++ /dev/null
@@ -1,81 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 766
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 192:
- format:
- bitrate = -1
- id = 192
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 1671
- sample count = 4
- sample 0:
- time = 29088
- flags = 1
- data = length 417, hash 5C710F78
- sample 1:
- time = 55210
- flags = 1
- data = length 418, hash 79CF71F8
- sample 2:
- time = 81332
- flags = 1
- data = length 418, hash 79CF71F8
- sample 3:
- time = 107454
- flags = 1
- data = length 418, hash 79CF71F8
-track 224:
- format:
- bitrate = -1
- id = 224
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash 743CC6F8
- total output bytes = 44056
- sample count = 2
- sample 0:
- time = 40000
- flags = 1
- data = length 20646, hash 576390B
- sample 1:
- time = 80000
- flags = 0
- data = length 17831, hash 5C5A57F5
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps.1.dump b/tree/library/extractor/src/test/assets/ts/sample.ps.1.dump
deleted file mode 100644
index 5fc9d32..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ps.1.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 766
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 192:
- format:
- bitrate = -1
- id = 192
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-track 224:
- format:
- bitrate = -1
- id = 224
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash 743CC6F8
- total output bytes = 33949
- sample count = 1
- sample 0:
- time = 80000
- flags = 0
- data = length 17831, hash 5C5A57F5
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps.2.dump b/tree/library/extractor/src/test/assets/ts/sample.ps.2.dump
deleted file mode 100644
index fefe6fa..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ps.2.dump
+++ /dev/null
@@ -1,57 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 766
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 192:
- format:
- bitrate = -1
- id = 192
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-track 224:
- format:
- bitrate = -1
- id = 224
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash 743CC6F8
- total output bytes = 19791
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps.3.dump b/tree/library/extractor/src/test/assets/ts/sample.ps.3.dump
deleted file mode 100644
index 5a97e3a..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ps.3.dump
+++ /dev/null
@@ -1,57 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 766
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 192:
- format:
- bitrate = -1
- id = 192
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-track 224:
- format:
- bitrate = -1
- id = 224
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash 743CC6F8
- total output bytes = 1585
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps.unklen.dump b/tree/library/extractor/src/test/assets/ts/sample.ps.unklen.dump
deleted file mode 100644
index 9e619e9..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ps.unklen.dump
+++ /dev/null
@@ -1,81 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 192:
- format:
- bitrate = -1
- id = 192
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 1671
- sample count = 4
- sample 0:
- time = 29088
- flags = 1
- data = length 417, hash 5C710F78
- sample 1:
- time = 55210
- flags = 1
- data = length 418, hash 79CF71F8
- sample 2:
- time = 81332
- flags = 1
- data = length 418, hash 79CF71F8
- sample 3:
- time = 107454
- flags = 1
- data = length 418, hash 79CF71F8
-track 224:
- format:
- bitrate = -1
- id = 224
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash 743CC6F8
- total output bytes = 44056
- sample count = 2
- sample 0:
- time = 40000
- flags = 1
- data = length 20646, hash 576390B
- sample 1:
- time = 80000
- flags = 0
- data = length 17831, hash 5C5A57F5
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts.0.dump b/tree/library/extractor/src/test/assets/ts/sample.ts.0.dump
deleted file mode 100644
index 4bc1978..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ts.0.dump
+++ /dev/null
@@ -1,106 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 66733
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 3
-track 256:
- format:
- bitrate = -1
- id = 1/256
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash CE183139
- total output bytes = 45026
- sample count = 2
- sample 0:
- time = 33366
- flags = 1
- data = length 20711, hash 34341E8
- sample 1:
- time = 66733
- flags = 0
- data = length 18112, hash EC44B35B
-track 257:
- format:
- bitrate = -1
- id = 1/257
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 5015
- sample count = 4
- sample 0:
- time = 22455
- flags = 1
- data = length 1253, hash 727FD1C6
- sample 1:
- time = 48577
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 2:
- time = 74700
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 3:
- time = 100822
- flags = 1
- data = length 1254, hash 73FB07B8
-track 8448:
- format:
- bitrate = -1
- id = 1/8448
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts.1.dump b/tree/library/extractor/src/test/assets/ts/sample.ts.1.dump
deleted file mode 100644
index 97471be..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ts.1.dump
+++ /dev/null
@@ -1,106 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 66733
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 3
-track 256:
- format:
- bitrate = -1
- id = 1/256
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash CE183139
- total output bytes = 45026
- sample count = 2
- sample 0:
- time = 55610
- flags = 1
- data = length 20711, hash 34341E8
- sample 1:
- time = 88977
- flags = 0
- data = length 18112, hash EC44B35B
-track 257:
- format:
- bitrate = -1
- id = 1/257
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 5015
- sample count = 4
- sample 0:
- time = 44699
- flags = 1
- data = length 1253, hash 727FD1C6
- sample 1:
- time = 70821
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 2:
- time = 96944
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 3:
- time = 123066
- flags = 1
- data = length 1254, hash 73FB07B8
-track 8448:
- format:
- bitrate = -1
- id = 1/8448
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts.2.dump b/tree/library/extractor/src/test/assets/ts/sample.ts.2.dump
deleted file mode 100644
index 7039c71..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ts.2.dump
+++ /dev/null
@@ -1,106 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 66733
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 3
-track 256:
- format:
- bitrate = -1
- id = 1/256
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash CE183139
- total output bytes = 45026
- sample count = 2
- sample 0:
- time = 77854
- flags = 1
- data = length 20711, hash 34341E8
- sample 1:
- time = 111221
- flags = 0
- data = length 18112, hash EC44B35B
-track 257:
- format:
- bitrate = -1
- id = 1/257
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 5015
- sample count = 4
- sample 0:
- time = 66943
- flags = 1
- data = length 1253, hash 727FD1C6
- sample 1:
- time = 93065
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 2:
- time = 119188
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 3:
- time = 145310
- flags = 1
- data = length 1254, hash 73FB07B8
-track 8448:
- format:
- bitrate = -1
- id = 1/8448
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts.3.dump b/tree/library/extractor/src/test/assets/ts/sample.ts.3.dump
deleted file mode 100644
index b6c1164..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ts.3.dump
+++ /dev/null
@@ -1,90 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 66733
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 3
-track 256:
- format:
- bitrate = -1
- id = 1/256
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash CE183139
- total output bytes = 0
- sample count = 0
-track 257:
- format:
- bitrate = -1
- id = 1/257
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2508
- sample count = 2
- sample 0:
- time = 66733
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 1:
- time = 92855
- flags = 1
- data = length 1254, hash 73FB07B8
-track 8448:
- format:
- bitrate = -1
- id = 1/8448
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts.unklen.dump b/tree/library/extractor/src/test/assets/ts/sample.ts.unklen.dump
deleted file mode 100644
index 904add8..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample.ts.unklen.dump
+++ /dev/null
@@ -1,106 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 3
-track 256:
- format:
- bitrate = -1
- id = 1/256
- containerMimeType = null
- sampleMimeType = video/mpeg2
- maxInputSize = -1
- width = 640
- height = 426
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 22, hash CE183139
- total output bytes = 45026
- sample count = 2
- sample 0:
- time = 33366
- flags = 1
- data = length 20711, hash 34341E8
- sample 1:
- time = 66733
- flags = 0
- data = length 18112, hash EC44B35B
-track 257:
- format:
- bitrate = -1
- id = 1/257
- containerMimeType = null
- sampleMimeType = audio/mpeg-L2
- maxInputSize = 4096
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = und
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 5015
- sample count = 4
- sample 0:
- time = 22455
- flags = 1
- data = length 1253, hash 727FD1C6
- sample 1:
- time = 48577
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 2:
- time = 74700
- flags = 1
- data = length 1254, hash 73FB07B8
- sample 3:
- time = 100822
- flags = 1
- data = length 1254, hash 73FB07B8
-track 8448:
- format:
- bitrate = -1
- id = 1/8448
- containerMimeType = null
- sampleMimeType = application/cea-608
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.0.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.0.dump
deleted file mode 100644
index c823eba..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.0.dump
+++ /dev/null
@@ -1,633 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3356772
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 30797
- sample count = 144
- sample 0:
- time = 0
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 23219
- flags = 1
- data = length 6, hash 31CF3A46
- sample 2:
- time = 46438
- flags = 1
- data = length 6, hash 31CF3A46
- sample 3:
- time = 69657
- flags = 1
- data = length 6, hash 31CF3A46
- sample 4:
- time = 92876
- flags = 1
- data = length 6, hash 31EC5206
- sample 5:
- time = 116095
- flags = 1
- data = length 171, hash 4F6478F6
- sample 6:
- time = 139314
- flags = 1
- data = length 202, hash AF4068A3
- sample 7:
- time = 162533
- flags = 1
- data = length 210, hash E4C10618
- sample 8:
- time = 185752
- flags = 1
- data = length 217, hash 9ECCD0D9
- sample 9:
- time = 208971
- flags = 1
- data = length 212, hash 6BAC2CD9
- sample 10:
- time = 232190
- flags = 1
- data = length 223, hash 188B6010
- sample 11:
- time = 255409
- flags = 1
- data = length 222, hash C1A04D0C
- sample 12:
- time = 278628
- flags = 1
- data = length 220, hash D65F9768
- sample 13:
- time = 301847
- flags = 1
- data = length 227, hash B96C9E14
- sample 14:
- time = 325066
- flags = 1
- data = length 229, hash 9FB09972
- sample 15:
- time = 348285
- flags = 1
- data = length 220, hash 2271F053
- sample 16:
- time = 371504
- flags = 1
- data = length 226, hash 5EDD2F4F
- sample 17:
- time = 394723
- flags = 1
- data = length 239, hash 957510E0
- sample 18:
- time = 417942
- flags = 1
- data = length 224, hash 718A8F47
- sample 19:
- time = 441161
- flags = 1
- data = length 225, hash 5E11E293
- sample 20:
- time = 464380
- flags = 1
- data = length 227, hash FCE50D27
- sample 21:
- time = 487599
- flags = 1
- data = length 212, hash 77908C40
- sample 22:
- time = 510818
- flags = 1
- data = length 227, hash 34C4EB32
- sample 23:
- time = 534037
- flags = 1
- data = length 231, hash 95488307
- sample 24:
- time = 557256
- flags = 1
- data = length 226, hash 97F12D6F
- sample 25:
- time = 580475
- flags = 1
- data = length 236, hash 91A9D9A2
- sample 26:
- time = 603694
- flags = 1
- data = length 227, hash 27A608F9
- sample 27:
- time = 626913
- flags = 1
- data = length 229, hash 57DAAE4
- sample 28:
- time = 650132
- flags = 1
- data = length 235, hash ED30AC34
- sample 29:
- time = 673351
- flags = 1
- data = length 227, hash BD3D6280
- sample 30:
- time = 696570
- flags = 1
- data = length 233, hash 694B1087
- sample 31:
- time = 719789
- flags = 1
- data = length 232, hash 1EDFE047
- sample 32:
- time = 743008
- flags = 1
- data = length 228, hash E2A831F4
- sample 33:
- time = 766227
- flags = 1
- data = length 231, hash 757E6012
- sample 34:
- time = 789446
- flags = 1
- data = length 223, hash 4003D791
- sample 35:
- time = 812665
- flags = 1
- data = length 232, hash 3CF9A07C
- sample 36:
- time = 835884
- flags = 1
- data = length 228, hash 25AC3FF7
- sample 37:
- time = 859103
- flags = 1
- data = length 220, hash 2C1824CE
- sample 38:
- time = 882322
- flags = 1
- data = length 229, hash 46FDD8FB
- sample 39:
- time = 905541
- flags = 1
- data = length 237, hash F6988018
- sample 40:
- time = 928760
- flags = 1
- data = length 242, hash 60436B6B
- sample 41:
- time = 951979
- flags = 1
- data = length 275, hash 90EDFA8E
- sample 42:
- time = 975198
- flags = 1
- data = length 242, hash 5C86EFCB
- sample 43:
- time = 998417
- flags = 1
- data = length 233, hash E0A51B82
- sample 44:
- time = 1021636
- flags = 1
- data = length 235, hash 590DF14F
- sample 45:
- time = 1044855
- flags = 1
- data = length 238, hash 69AF4E6E
- sample 46:
- time = 1068074
- flags = 1
- data = length 235, hash E745AE8D
- sample 47:
- time = 1091293
- flags = 1
- data = length 223, hash 295F2A13
- sample 48:
- time = 1114512
- flags = 1
- data = length 228, hash E2F47B21
- sample 49:
- time = 1137731
- flags = 1
- data = length 229, hash 262C3CFE
- sample 50:
- time = 1160950
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 51:
- time = 1184169
- flags = 1
- data = length 233, hash F3D80836
- sample 52:
- time = 1207388
- flags = 1
- data = length 237, hash 32E0A11E
- sample 53:
- time = 1230607
- flags = 1
- data = length 228, hash E1B89F13
- sample 54:
- time = 1253826
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 55:
- time = 1277045
- flags = 1
- data = length 235, hash 3C84161F
- sample 56:
- time = 1300264
- flags = 1
- data = length 227, hash A47E1789
- sample 57:
- time = 1323483
- flags = 1
- data = length 228, hash 869FDFD3
- sample 58:
- time = 1346702
- flags = 1
- data = length 233, hash 272ECE2
- sample 59:
- time = 1369921
- flags = 1
- data = length 227, hash DB6B9618
- sample 60:
- time = 1393140
- flags = 1
- data = length 212, hash 63214325
- sample 61:
- time = 1416359
- flags = 1
- data = length 221, hash 9BA588A1
- sample 62:
- time = 1439578
- flags = 1
- data = length 225, hash 21EFD50C
- sample 63:
- time = 1462797
- flags = 1
- data = length 231, hash F3AD0BF
- sample 64:
- time = 1486016
- flags = 1
- data = length 224, hash 822C9210
- sample 65:
- time = 1509235
- flags = 1
- data = length 195, hash D4EF53EE
- sample 66:
- time = 1532454
- flags = 1
- data = length 195, hash A816647A
- sample 67:
- time = 1555673
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 68:
- time = 1578892
- flags = 1
- data = length 210, hash 956E3600
- sample 69:
- time = 1602111
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 70:
- time = 1625330
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 71:
- time = 1648549
- flags = 1
- data = length 228, hash F3B70641
- sample 72:
- time = 1671768
- flags = 1
- data = length 237, hash 124E3194
- sample 73:
- time = 1694987
- flags = 1
- data = length 231, hash 950CD7C8
- sample 74:
- time = 1718206
- flags = 1
- data = length 236, hash A12E49AF
- sample 75:
- time = 1741425
- flags = 1
- data = length 242, hash 43BC9C24
- sample 76:
- time = 1764644
- flags = 1
- data = length 241, hash DCF0B17
- sample 77:
- time = 1787863
- flags = 1
- data = length 251, hash C0B99968
- sample 78:
- time = 1811082
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 79:
- time = 1834301
- flags = 1
- data = length 238, hash 1BA69079
- sample 80:
- time = 1857520
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 81:
- time = 1880739
- flags = 1
- data = length 231, hash EABBEE02
- sample 82:
- time = 1903958
- flags = 1
- data = length 226, hash D09C44FB
- sample 83:
- time = 1927177
- flags = 1
- data = length 235, hash BE6A6608
- sample 84:
- time = 1950396
- flags = 1
- data = length 235, hash 2735F454
- sample 85:
- time = 1973615
- flags = 1
- data = length 238, hash B160DFE7
- sample 86:
- time = 1996834
- flags = 1
- data = length 232, hash 1B217D2E
- sample 87:
- time = 2020053
- flags = 1
- data = length 251, hash D1C14CEA
- sample 88:
- time = 2043272
- flags = 1
- data = length 256, hash 97C87F08
- sample 89:
- time = 2066491
- flags = 1
- data = length 237, hash 6645DB3
- sample 90:
- time = 2089710
- flags = 1
- data = length 235, hash 727A1C82
- sample 91:
- time = 2112929
- flags = 1
- data = length 234, hash 5015F8B5
- sample 92:
- time = 2136148
- flags = 1
- data = length 241, hash 9102144B
- sample 93:
- time = 2159367
- flags = 1
- data = length 224, hash 64E0D807
- sample 94:
- time = 2182586
- flags = 1
- data = length 228, hash 1922B852
- sample 95:
- time = 2205805
- flags = 1
- data = length 224, hash 953502D8
- sample 96:
- time = 2229024
- flags = 1
- data = length 214, hash 92B87FE7
- sample 97:
- time = 2252243
- flags = 1
- data = length 213, hash BB0C8D86
- sample 98:
- time = 2275462
- flags = 1
- data = length 206, hash 9AD21017
- sample 99:
- time = 2298681
- flags = 1
- data = length 209, hash C479FE94
- sample 100:
- time = 2321900
- flags = 1
- data = length 220, hash 3033DCE1
- sample 101:
- time = 2345119
- flags = 1
- data = length 217, hash 7D589C94
- sample 102:
- time = 2368338
- flags = 1
- data = length 216, hash AAF6C183
- sample 103:
- time = 2391557
- flags = 1
- data = length 206, hash 1EE1207F
- sample 104:
- time = 2414776
- flags = 1
- data = length 204, hash 4BEB1210
- sample 105:
- time = 2437995
- flags = 1
- data = length 213, hash 21A841C9
- sample 106:
- time = 2461214
- flags = 1
- data = length 207, hash B80B0424
- sample 107:
- time = 2484433
- flags = 1
- data = length 212, hash 4785A1C3
- sample 108:
- time = 2507652
- flags = 1
- data = length 205, hash 59BF7229
- sample 109:
- time = 2530871
- flags = 1
- data = length 208, hash FA313DDE
- sample 110:
- time = 2554090
- flags = 1
- data = length 211, hash 190D85FD
- sample 111:
- time = 2577309
- flags = 1
- data = length 211, hash BA050052
- sample 112:
- time = 2600528
- flags = 1
- data = length 211, hash F3080F10
- sample 113:
- time = 2623747
- flags = 1
- data = length 210, hash F41B7BE7
- sample 114:
- time = 2646966
- flags = 1
- data = length 207, hash 2176C97E
- sample 115:
- time = 2670185
- flags = 1
- data = length 220, hash 32087455
- sample 116:
- time = 2693404
- flags = 1
- data = length 213, hash 4E5649A8
- sample 117:
- time = 2716623
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 118:
- time = 2739842
- flags = 1
- data = length 204, hash 1E895C2A
- sample 119:
- time = 2763061
- flags = 1
- data = length 219, hash 45382270
- sample 120:
- time = 2786280
- flags = 1
- data = length 205, hash D66C6A1D
- sample 121:
- time = 2809499
- flags = 1
- data = length 204, hash 467AD01F
- sample 122:
- time = 2832718
- flags = 1
- data = length 211, hash F0435574
- sample 123:
- time = 2855937
- flags = 1
- data = length 206, hash 8C96B75F
- sample 124:
- time = 2879156
- flags = 1
- data = length 200, hash 82553248
- sample 125:
- time = 2902375
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 126:
- time = 2925594
- flags = 1
- data = length 196, hash 33151DC4
- sample 127:
- time = 2948813
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 128:
- time = 2972032
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 129:
- time = 2995251
- flags = 1
- data = length 209, hash A72FABAA
- sample 130:
- time = 3018470
- flags = 1
- data = length 217, hash BA33B985
- sample 131:
- time = 3041689
- flags = 1
- data = length 235, hash 9919CFD9
- sample 132:
- time = 3064908
- flags = 1
- data = length 236, hash A22C7267
- sample 133:
- time = 3088127
- flags = 1
- data = length 213, hash 3D57C901
- sample 134:
- time = 3111346
- flags = 1
- data = length 205, hash 47F68FDE
- sample 135:
- time = 3134565
- flags = 1
- data = length 210, hash 9A756E9C
- sample 136:
- time = 3157784
- flags = 1
- data = length 210, hash BD45C31F
- sample 137:
- time = 3181003
- flags = 1
- data = length 207, hash 8774FF7B
- sample 138:
- time = 3204222
- flags = 1
- data = length 149, hash 4678C0E5
- sample 139:
- time = 3227441
- flags = 1
- data = length 161, hash E991035D
- sample 140:
- time = 3250660
- flags = 1
- data = length 197, hash C3013689
- sample 141:
- time = 3273879
- flags = 1
- data = length 208, hash E6C0237
- sample 142:
- time = 3297098
- flags = 1
- data = length 232, hash A330F188
- sample 143:
- time = 3320317
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.1.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.1.dump
deleted file mode 100644
index ce0a645..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.1.dump
+++ /dev/null
@@ -1,433 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3356772
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 20533
- sample count = 94
- sample 0:
- time = 1118924
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 1:
- time = 1142143
- flags = 1
- data = length 233, hash F3D80836
- sample 2:
- time = 1165362
- flags = 1
- data = length 237, hash 32E0A11E
- sample 3:
- time = 1188581
- flags = 1
- data = length 228, hash E1B89F13
- sample 4:
- time = 1211800
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 5:
- time = 1235019
- flags = 1
- data = length 235, hash 3C84161F
- sample 6:
- time = 1258238
- flags = 1
- data = length 227, hash A47E1789
- sample 7:
- time = 1281457
- flags = 1
- data = length 228, hash 869FDFD3
- sample 8:
- time = 1304676
- flags = 1
- data = length 233, hash 272ECE2
- sample 9:
- time = 1327895
- flags = 1
- data = length 227, hash DB6B9618
- sample 10:
- time = 1351114
- flags = 1
- data = length 212, hash 63214325
- sample 11:
- time = 1374333
- flags = 1
- data = length 221, hash 9BA588A1
- sample 12:
- time = 1397552
- flags = 1
- data = length 225, hash 21EFD50C
- sample 13:
- time = 1420771
- flags = 1
- data = length 231, hash F3AD0BF
- sample 14:
- time = 1443990
- flags = 1
- data = length 224, hash 822C9210
- sample 15:
- time = 1467209
- flags = 1
- data = length 195, hash D4EF53EE
- sample 16:
- time = 1490428
- flags = 1
- data = length 195, hash A816647A
- sample 17:
- time = 1513647
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 18:
- time = 1536866
- flags = 1
- data = length 210, hash 956E3600
- sample 19:
- time = 1560085
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 20:
- time = 1583304
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 21:
- time = 1606523
- flags = 1
- data = length 228, hash F3B70641
- sample 22:
- time = 1629742
- flags = 1
- data = length 237, hash 124E3194
- sample 23:
- time = 1652961
- flags = 1
- data = length 231, hash 950CD7C8
- sample 24:
- time = 1676180
- flags = 1
- data = length 236, hash A12E49AF
- sample 25:
- time = 1699399
- flags = 1
- data = length 242, hash 43BC9C24
- sample 26:
- time = 1722618
- flags = 1
- data = length 241, hash DCF0B17
- sample 27:
- time = 1745837
- flags = 1
- data = length 251, hash C0B99968
- sample 28:
- time = 1769056
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 29:
- time = 1792275
- flags = 1
- data = length 238, hash 1BA69079
- sample 30:
- time = 1815494
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 31:
- time = 1838713
- flags = 1
- data = length 231, hash EABBEE02
- sample 32:
- time = 1861932
- flags = 1
- data = length 226, hash D09C44FB
- sample 33:
- time = 1885151
- flags = 1
- data = length 235, hash BE6A6608
- sample 34:
- time = 1908370
- flags = 1
- data = length 235, hash 2735F454
- sample 35:
- time = 1931589
- flags = 1
- data = length 238, hash B160DFE7
- sample 36:
- time = 1954808
- flags = 1
- data = length 232, hash 1B217D2E
- sample 37:
- time = 1978027
- flags = 1
- data = length 251, hash D1C14CEA
- sample 38:
- time = 2001246
- flags = 1
- data = length 256, hash 97C87F08
- sample 39:
- time = 2024465
- flags = 1
- data = length 237, hash 6645DB3
- sample 40:
- time = 2047684
- flags = 1
- data = length 235, hash 727A1C82
- sample 41:
- time = 2070903
- flags = 1
- data = length 234, hash 5015F8B5
- sample 42:
- time = 2094122
- flags = 1
- data = length 241, hash 9102144B
- sample 43:
- time = 2117341
- flags = 1
- data = length 224, hash 64E0D807
- sample 44:
- time = 2140560
- flags = 1
- data = length 228, hash 1922B852
- sample 45:
- time = 2163779
- flags = 1
- data = length 224, hash 953502D8
- sample 46:
- time = 2186998
- flags = 1
- data = length 214, hash 92B87FE7
- sample 47:
- time = 2210217
- flags = 1
- data = length 213, hash BB0C8D86
- sample 48:
- time = 2233436
- flags = 1
- data = length 206, hash 9AD21017
- sample 49:
- time = 2256655
- flags = 1
- data = length 209, hash C479FE94
- sample 50:
- time = 2279874
- flags = 1
- data = length 220, hash 3033DCE1
- sample 51:
- time = 2303093
- flags = 1
- data = length 217, hash 7D589C94
- sample 52:
- time = 2326312
- flags = 1
- data = length 216, hash AAF6C183
- sample 53:
- time = 2349531
- flags = 1
- data = length 206, hash 1EE1207F
- sample 54:
- time = 2372750
- flags = 1
- data = length 204, hash 4BEB1210
- sample 55:
- time = 2395969
- flags = 1
- data = length 213, hash 21A841C9
- sample 56:
- time = 2419188
- flags = 1
- data = length 207, hash B80B0424
- sample 57:
- time = 2442407
- flags = 1
- data = length 212, hash 4785A1C3
- sample 58:
- time = 2465626
- flags = 1
- data = length 205, hash 59BF7229
- sample 59:
- time = 2488845
- flags = 1
- data = length 208, hash FA313DDE
- sample 60:
- time = 2512064
- flags = 1
- data = length 211, hash 190D85FD
- sample 61:
- time = 2535283
- flags = 1
- data = length 211, hash BA050052
- sample 62:
- time = 2558502
- flags = 1
- data = length 211, hash F3080F10
- sample 63:
- time = 2581721
- flags = 1
- data = length 210, hash F41B7BE7
- sample 64:
- time = 2604940
- flags = 1
- data = length 207, hash 2176C97E
- sample 65:
- time = 2628159
- flags = 1
- data = length 220, hash 32087455
- sample 66:
- time = 2651378
- flags = 1
- data = length 213, hash 4E5649A8
- sample 67:
- time = 2674597
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 68:
- time = 2697816
- flags = 1
- data = length 204, hash 1E895C2A
- sample 69:
- time = 2721035
- flags = 1
- data = length 219, hash 45382270
- sample 70:
- time = 2744254
- flags = 1
- data = length 205, hash D66C6A1D
- sample 71:
- time = 2767473
- flags = 1
- data = length 204, hash 467AD01F
- sample 72:
- time = 2790692
- flags = 1
- data = length 211, hash F0435574
- sample 73:
- time = 2813911
- flags = 1
- data = length 206, hash 8C96B75F
- sample 74:
- time = 2837130
- flags = 1
- data = length 200, hash 82553248
- sample 75:
- time = 2860349
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 76:
- time = 2883568
- flags = 1
- data = length 196, hash 33151DC4
- sample 77:
- time = 2906787
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 78:
- time = 2930006
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 79:
- time = 2953225
- flags = 1
- data = length 209, hash A72FABAA
- sample 80:
- time = 2976444
- flags = 1
- data = length 217, hash BA33B985
- sample 81:
- time = 2999663
- flags = 1
- data = length 235, hash 9919CFD9
- sample 82:
- time = 3022882
- flags = 1
- data = length 236, hash A22C7267
- sample 83:
- time = 3046101
- flags = 1
- data = length 213, hash 3D57C901
- sample 84:
- time = 3069320
- flags = 1
- data = length 205, hash 47F68FDE
- sample 85:
- time = 3092539
- flags = 1
- data = length 210, hash 9A756E9C
- sample 86:
- time = 3115758
- flags = 1
- data = length 210, hash BD45C31F
- sample 87:
- time = 3138977
- flags = 1
- data = length 207, hash 8774FF7B
- sample 88:
- time = 3162196
- flags = 1
- data = length 149, hash 4678C0E5
- sample 89:
- time = 3185415
- flags = 1
- data = length 161, hash E991035D
- sample 90:
- time = 3208634
- flags = 1
- data = length 197, hash C3013689
- sample 91:
- time = 3231853
- flags = 1
- data = length 208, hash E6C0237
- sample 92:
- time = 3255072
- flags = 1
- data = length 232, hash A330F188
- sample 93:
- time = 3278291
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.2.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.2.dump
deleted file mode 100644
index efaa192..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.2.dump
+++ /dev/null
@@ -1,253 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3356772
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 10161
- sample count = 49
- sample 0:
- time = 2237848
- flags = 1
- data = length 224, hash 953502D8
- sample 1:
- time = 2261067
- flags = 1
- data = length 214, hash 92B87FE7
- sample 2:
- time = 2284286
- flags = 1
- data = length 213, hash BB0C8D86
- sample 3:
- time = 2307505
- flags = 1
- data = length 206, hash 9AD21017
- sample 4:
- time = 2330724
- flags = 1
- data = length 209, hash C479FE94
- sample 5:
- time = 2353943
- flags = 1
- data = length 220, hash 3033DCE1
- sample 6:
- time = 2377162
- flags = 1
- data = length 217, hash 7D589C94
- sample 7:
- time = 2400381
- flags = 1
- data = length 216, hash AAF6C183
- sample 8:
- time = 2423600
- flags = 1
- data = length 206, hash 1EE1207F
- sample 9:
- time = 2446819
- flags = 1
- data = length 204, hash 4BEB1210
- sample 10:
- time = 2470038
- flags = 1
- data = length 213, hash 21A841C9
- sample 11:
- time = 2493257
- flags = 1
- data = length 207, hash B80B0424
- sample 12:
- time = 2516476
- flags = 1
- data = length 212, hash 4785A1C3
- sample 13:
- time = 2539695
- flags = 1
- data = length 205, hash 59BF7229
- sample 14:
- time = 2562914
- flags = 1
- data = length 208, hash FA313DDE
- sample 15:
- time = 2586133
- flags = 1
- data = length 211, hash 190D85FD
- sample 16:
- time = 2609352
- flags = 1
- data = length 211, hash BA050052
- sample 17:
- time = 2632571
- flags = 1
- data = length 211, hash F3080F10
- sample 18:
- time = 2655790
- flags = 1
- data = length 210, hash F41B7BE7
- sample 19:
- time = 2679009
- flags = 1
- data = length 207, hash 2176C97E
- sample 20:
- time = 2702228
- flags = 1
- data = length 220, hash 32087455
- sample 21:
- time = 2725447
- flags = 1
- data = length 213, hash 4E5649A8
- sample 22:
- time = 2748666
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 23:
- time = 2771885
- flags = 1
- data = length 204, hash 1E895C2A
- sample 24:
- time = 2795104
- flags = 1
- data = length 219, hash 45382270
- sample 25:
- time = 2818323
- flags = 1
- data = length 205, hash D66C6A1D
- sample 26:
- time = 2841542
- flags = 1
- data = length 204, hash 467AD01F
- sample 27:
- time = 2864761
- flags = 1
- data = length 211, hash F0435574
- sample 28:
- time = 2887980
- flags = 1
- data = length 206, hash 8C96B75F
- sample 29:
- time = 2911199
- flags = 1
- data = length 200, hash 82553248
- sample 30:
- time = 2934418
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 31:
- time = 2957637
- flags = 1
- data = length 196, hash 33151DC4
- sample 32:
- time = 2980856
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 33:
- time = 3004075
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 34:
- time = 3027294
- flags = 1
- data = length 209, hash A72FABAA
- sample 35:
- time = 3050513
- flags = 1
- data = length 217, hash BA33B985
- sample 36:
- time = 3073732
- flags = 1
- data = length 235, hash 9919CFD9
- sample 37:
- time = 3096951
- flags = 1
- data = length 236, hash A22C7267
- sample 38:
- time = 3120170
- flags = 1
- data = length 213, hash 3D57C901
- sample 39:
- time = 3143389
- flags = 1
- data = length 205, hash 47F68FDE
- sample 40:
- time = 3166608
- flags = 1
- data = length 210, hash 9A756E9C
- sample 41:
- time = 3189827
- flags = 1
- data = length 210, hash BD45C31F
- sample 42:
- time = 3213046
- flags = 1
- data = length 207, hash 8774FF7B
- sample 43:
- time = 3236265
- flags = 1
- data = length 149, hash 4678C0E5
- sample 44:
- time = 3259484
- flags = 1
- data = length 161, hash E991035D
- sample 45:
- time = 3282703
- flags = 1
- data = length 197, hash C3013689
- sample 46:
- time = 3305922
- flags = 1
- data = length 208, hash E6C0237
- sample 47:
- time = 3329141
- flags = 1
- data = length 232, hash A330F188
- sample 48:
- time = 3352360
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.3.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.3.dump
deleted file mode 100644
index 892445c..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.3.dump
+++ /dev/null
@@ -1,61 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3356772
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 174
- sample count = 1
- sample 0:
- time = 3356772
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.unklen.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.unklen.dump
deleted file mode 100644
index 9b24043..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts.unklen.dump
+++ /dev/null
@@ -1,633 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 30797
- sample count = 144
- sample 0:
- time = 0
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 23219
- flags = 1
- data = length 6, hash 31CF3A46
- sample 2:
- time = 46438
- flags = 1
- data = length 6, hash 31CF3A46
- sample 3:
- time = 69657
- flags = 1
- data = length 6, hash 31CF3A46
- sample 4:
- time = 92876
- flags = 1
- data = length 6, hash 31EC5206
- sample 5:
- time = 116095
- flags = 1
- data = length 171, hash 4F6478F6
- sample 6:
- time = 139314
- flags = 1
- data = length 202, hash AF4068A3
- sample 7:
- time = 162533
- flags = 1
- data = length 210, hash E4C10618
- sample 8:
- time = 185752
- flags = 1
- data = length 217, hash 9ECCD0D9
- sample 9:
- time = 208971
- flags = 1
- data = length 212, hash 6BAC2CD9
- sample 10:
- time = 232190
- flags = 1
- data = length 223, hash 188B6010
- sample 11:
- time = 255409
- flags = 1
- data = length 222, hash C1A04D0C
- sample 12:
- time = 278628
- flags = 1
- data = length 220, hash D65F9768
- sample 13:
- time = 301847
- flags = 1
- data = length 227, hash B96C9E14
- sample 14:
- time = 325066
- flags = 1
- data = length 229, hash 9FB09972
- sample 15:
- time = 348285
- flags = 1
- data = length 220, hash 2271F053
- sample 16:
- time = 371504
- flags = 1
- data = length 226, hash 5EDD2F4F
- sample 17:
- time = 394723
- flags = 1
- data = length 239, hash 957510E0
- sample 18:
- time = 417942
- flags = 1
- data = length 224, hash 718A8F47
- sample 19:
- time = 441161
- flags = 1
- data = length 225, hash 5E11E293
- sample 20:
- time = 464380
- flags = 1
- data = length 227, hash FCE50D27
- sample 21:
- time = 487599
- flags = 1
- data = length 212, hash 77908C40
- sample 22:
- time = 510818
- flags = 1
- data = length 227, hash 34C4EB32
- sample 23:
- time = 534037
- flags = 1
- data = length 231, hash 95488307
- sample 24:
- time = 557256
- flags = 1
- data = length 226, hash 97F12D6F
- sample 25:
- time = 580475
- flags = 1
- data = length 236, hash 91A9D9A2
- sample 26:
- time = 603694
- flags = 1
- data = length 227, hash 27A608F9
- sample 27:
- time = 626913
- flags = 1
- data = length 229, hash 57DAAE4
- sample 28:
- time = 650132
- flags = 1
- data = length 235, hash ED30AC34
- sample 29:
- time = 673351
- flags = 1
- data = length 227, hash BD3D6280
- sample 30:
- time = 696570
- flags = 1
- data = length 233, hash 694B1087
- sample 31:
- time = 719789
- flags = 1
- data = length 232, hash 1EDFE047
- sample 32:
- time = 743008
- flags = 1
- data = length 228, hash E2A831F4
- sample 33:
- time = 766227
- flags = 1
- data = length 231, hash 757E6012
- sample 34:
- time = 789446
- flags = 1
- data = length 223, hash 4003D791
- sample 35:
- time = 812665
- flags = 1
- data = length 232, hash 3CF9A07C
- sample 36:
- time = 835884
- flags = 1
- data = length 228, hash 25AC3FF7
- sample 37:
- time = 859103
- flags = 1
- data = length 220, hash 2C1824CE
- sample 38:
- time = 882322
- flags = 1
- data = length 229, hash 46FDD8FB
- sample 39:
- time = 905541
- flags = 1
- data = length 237, hash F6988018
- sample 40:
- time = 928760
- flags = 1
- data = length 242, hash 60436B6B
- sample 41:
- time = 951979
- flags = 1
- data = length 275, hash 90EDFA8E
- sample 42:
- time = 975198
- flags = 1
- data = length 242, hash 5C86EFCB
- sample 43:
- time = 998417
- flags = 1
- data = length 233, hash E0A51B82
- sample 44:
- time = 1021636
- flags = 1
- data = length 235, hash 590DF14F
- sample 45:
- time = 1044855
- flags = 1
- data = length 238, hash 69AF4E6E
- sample 46:
- time = 1068074
- flags = 1
- data = length 235, hash E745AE8D
- sample 47:
- time = 1091293
- flags = 1
- data = length 223, hash 295F2A13
- sample 48:
- time = 1114512
- flags = 1
- data = length 228, hash E2F47B21
- sample 49:
- time = 1137731
- flags = 1
- data = length 229, hash 262C3CFE
- sample 50:
- time = 1160950
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 51:
- time = 1184169
- flags = 1
- data = length 233, hash F3D80836
- sample 52:
- time = 1207388
- flags = 1
- data = length 237, hash 32E0A11E
- sample 53:
- time = 1230607
- flags = 1
- data = length 228, hash E1B89F13
- sample 54:
- time = 1253826
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 55:
- time = 1277045
- flags = 1
- data = length 235, hash 3C84161F
- sample 56:
- time = 1300264
- flags = 1
- data = length 227, hash A47E1789
- sample 57:
- time = 1323483
- flags = 1
- data = length 228, hash 869FDFD3
- sample 58:
- time = 1346702
- flags = 1
- data = length 233, hash 272ECE2
- sample 59:
- time = 1369921
- flags = 1
- data = length 227, hash DB6B9618
- sample 60:
- time = 1393140
- flags = 1
- data = length 212, hash 63214325
- sample 61:
- time = 1416359
- flags = 1
- data = length 221, hash 9BA588A1
- sample 62:
- time = 1439578
- flags = 1
- data = length 225, hash 21EFD50C
- sample 63:
- time = 1462797
- flags = 1
- data = length 231, hash F3AD0BF
- sample 64:
- time = 1486016
- flags = 1
- data = length 224, hash 822C9210
- sample 65:
- time = 1509235
- flags = 1
- data = length 195, hash D4EF53EE
- sample 66:
- time = 1532454
- flags = 1
- data = length 195, hash A816647A
- sample 67:
- time = 1555673
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 68:
- time = 1578892
- flags = 1
- data = length 210, hash 956E3600
- sample 69:
- time = 1602111
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 70:
- time = 1625330
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 71:
- time = 1648549
- flags = 1
- data = length 228, hash F3B70641
- sample 72:
- time = 1671768
- flags = 1
- data = length 237, hash 124E3194
- sample 73:
- time = 1694987
- flags = 1
- data = length 231, hash 950CD7C8
- sample 74:
- time = 1718206
- flags = 1
- data = length 236, hash A12E49AF
- sample 75:
- time = 1741425
- flags = 1
- data = length 242, hash 43BC9C24
- sample 76:
- time = 1764644
- flags = 1
- data = length 241, hash DCF0B17
- sample 77:
- time = 1787863
- flags = 1
- data = length 251, hash C0B99968
- sample 78:
- time = 1811082
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 79:
- time = 1834301
- flags = 1
- data = length 238, hash 1BA69079
- sample 80:
- time = 1857520
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 81:
- time = 1880739
- flags = 1
- data = length 231, hash EABBEE02
- sample 82:
- time = 1903958
- flags = 1
- data = length 226, hash D09C44FB
- sample 83:
- time = 1927177
- flags = 1
- data = length 235, hash BE6A6608
- sample 84:
- time = 1950396
- flags = 1
- data = length 235, hash 2735F454
- sample 85:
- time = 1973615
- flags = 1
- data = length 238, hash B160DFE7
- sample 86:
- time = 1996834
- flags = 1
- data = length 232, hash 1B217D2E
- sample 87:
- time = 2020053
- flags = 1
- data = length 251, hash D1C14CEA
- sample 88:
- time = 2043272
- flags = 1
- data = length 256, hash 97C87F08
- sample 89:
- time = 2066491
- flags = 1
- data = length 237, hash 6645DB3
- sample 90:
- time = 2089710
- flags = 1
- data = length 235, hash 727A1C82
- sample 91:
- time = 2112929
- flags = 1
- data = length 234, hash 5015F8B5
- sample 92:
- time = 2136148
- flags = 1
- data = length 241, hash 9102144B
- sample 93:
- time = 2159367
- flags = 1
- data = length 224, hash 64E0D807
- sample 94:
- time = 2182586
- flags = 1
- data = length 228, hash 1922B852
- sample 95:
- time = 2205805
- flags = 1
- data = length 224, hash 953502D8
- sample 96:
- time = 2229024
- flags = 1
- data = length 214, hash 92B87FE7
- sample 97:
- time = 2252243
- flags = 1
- data = length 213, hash BB0C8D86
- sample 98:
- time = 2275462
- flags = 1
- data = length 206, hash 9AD21017
- sample 99:
- time = 2298681
- flags = 1
- data = length 209, hash C479FE94
- sample 100:
- time = 2321900
- flags = 1
- data = length 220, hash 3033DCE1
- sample 101:
- time = 2345119
- flags = 1
- data = length 217, hash 7D589C94
- sample 102:
- time = 2368338
- flags = 1
- data = length 216, hash AAF6C183
- sample 103:
- time = 2391557
- flags = 1
- data = length 206, hash 1EE1207F
- sample 104:
- time = 2414776
- flags = 1
- data = length 204, hash 4BEB1210
- sample 105:
- time = 2437995
- flags = 1
- data = length 213, hash 21A841C9
- sample 106:
- time = 2461214
- flags = 1
- data = length 207, hash B80B0424
- sample 107:
- time = 2484433
- flags = 1
- data = length 212, hash 4785A1C3
- sample 108:
- time = 2507652
- flags = 1
- data = length 205, hash 59BF7229
- sample 109:
- time = 2530871
- flags = 1
- data = length 208, hash FA313DDE
- sample 110:
- time = 2554090
- flags = 1
- data = length 211, hash 190D85FD
- sample 111:
- time = 2577309
- flags = 1
- data = length 211, hash BA050052
- sample 112:
- time = 2600528
- flags = 1
- data = length 211, hash F3080F10
- sample 113:
- time = 2623747
- flags = 1
- data = length 210, hash F41B7BE7
- sample 114:
- time = 2646966
- flags = 1
- data = length 207, hash 2176C97E
- sample 115:
- time = 2670185
- flags = 1
- data = length 220, hash 32087455
- sample 116:
- time = 2693404
- flags = 1
- data = length 213, hash 4E5649A8
- sample 117:
- time = 2716623
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 118:
- time = 2739842
- flags = 1
- data = length 204, hash 1E895C2A
- sample 119:
- time = 2763061
- flags = 1
- data = length 219, hash 45382270
- sample 120:
- time = 2786280
- flags = 1
- data = length 205, hash D66C6A1D
- sample 121:
- time = 2809499
- flags = 1
- data = length 204, hash 467AD01F
- sample 122:
- time = 2832718
- flags = 1
- data = length 211, hash F0435574
- sample 123:
- time = 2855937
- flags = 1
- data = length 206, hash 8C96B75F
- sample 124:
- time = 2879156
- flags = 1
- data = length 200, hash 82553248
- sample 125:
- time = 2902375
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 126:
- time = 2925594
- flags = 1
- data = length 196, hash 33151DC4
- sample 127:
- time = 2948813
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 128:
- time = 2972032
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 129:
- time = 2995251
- flags = 1
- data = length 209, hash A72FABAA
- sample 130:
- time = 3018470
- flags = 1
- data = length 217, hash BA33B985
- sample 131:
- time = 3041689
- flags = 1
- data = length 235, hash 9919CFD9
- sample 132:
- time = 3064908
- flags = 1
- data = length 236, hash A22C7267
- sample 133:
- time = 3088127
- flags = 1
- data = length 213, hash 3D57C901
- sample 134:
- time = 3111346
- flags = 1
- data = length 205, hash 47F68FDE
- sample 135:
- time = 3134565
- flags = 1
- data = length 210, hash 9A756E9C
- sample 136:
- time = 3157784
- flags = 1
- data = length 210, hash BD45C31F
- sample 137:
- time = 3181003
- flags = 1
- data = length 207, hash 8774FF7B
- sample 138:
- time = 3204222
- flags = 1
- data = length 149, hash 4678C0E5
- sample 139:
- time = 3227441
- flags = 1
- data = length 161, hash E991035D
- sample 140:
- time = 3250660
- flags = 1
- data = length 197, hash C3013689
- sample 141:
- time = 3273879
- flags = 1
- data = length 208, hash E6C0237
- sample 142:
- time = 3297098
- flags = 1
- data = length 232, hash A330F188
- sample 143:
- time = 3320317
- flags = 1
- data = length 174, hash 2B69C34E
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.0.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.0.dump
deleted file mode 100644
index cf83960..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.0.dump
+++ /dev/null
@@ -1,629 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3355717
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 30787
- sample count = 143
- sample 0:
- time = 0
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 23219
- flags = 1
- data = length 6, hash 31CF3A46
- sample 2:
- time = 46438
- flags = 1
- data = length 6, hash 31CF3A46
- sample 3:
- time = 69657
- flags = 1
- data = length 6, hash 31CF3A46
- sample 4:
- time = 92876
- flags = 1
- data = length 6, hash 31EC5206
- sample 5:
- time = 116095
- flags = 1
- data = length 171, hash 4F6478F6
- sample 6:
- time = 139314
- flags = 1
- data = length 202, hash AF4068A3
- sample 7:
- time = 162533
- flags = 1
- data = length 210, hash E4C10618
- sample 8:
- time = 185752
- flags = 1
- data = length 217, hash 9ECCD0D9
- sample 9:
- time = 208971
- flags = 1
- data = length 212, hash 6BAC2CD9
- sample 10:
- time = 232190
- flags = 1
- data = length 223, hash 188B6010
- sample 11:
- time = 255409
- flags = 1
- data = length 222, hash C1A04D0C
- sample 12:
- time = 278628
- flags = 1
- data = length 220, hash D65F9768
- sample 13:
- time = 301847
- flags = 1
- data = length 227, hash B96C9E14
- sample 14:
- time = 325066
- flags = 1
- data = length 229, hash 9FB09972
- sample 15:
- time = 348285
- flags = 1
- data = length 220, hash 2271F053
- sample 16:
- time = 371504
- flags = 1
- data = length 226, hash 5EDD2F4F
- sample 17:
- time = 394723
- flags = 1
- data = length 239, hash 957510E0
- sample 18:
- time = 417942
- flags = 1
- data = length 224, hash 718A8F47
- sample 19:
- time = 441161
- flags = 1
- data = length 225, hash 5E11E293
- sample 20:
- time = 464380
- flags = 1
- data = length 227, hash FCE50D27
- sample 21:
- time = 487599
- flags = 1
- data = length 212, hash 77908C40
- sample 22:
- time = 510818
- flags = 1
- data = length 227, hash 34C4EB32
- sample 23:
- time = 534037
- flags = 1
- data = length 231, hash 95488307
- sample 24:
- time = 557256
- flags = 1
- data = length 226, hash 97F12D6F
- sample 25:
- time = 580475
- flags = 1
- data = length 236, hash 91A9D9A2
- sample 26:
- time = 603694
- flags = 1
- data = length 227, hash 27A608F9
- sample 27:
- time = 626913
- flags = 1
- data = length 229, hash 57DAAE4
- sample 28:
- time = 650132
- flags = 1
- data = length 235, hash ED30AC34
- sample 29:
- time = 673351
- flags = 1
- data = length 227, hash BD3D6280
- sample 30:
- time = 696570
- flags = 1
- data = length 233, hash 694B1087
- sample 31:
- time = 719789
- flags = 1
- data = length 232, hash 1EDFE047
- sample 32:
- time = 743008
- flags = 1
- data = length 228, hash E2A831F4
- sample 33:
- time = 766227
- flags = 1
- data = length 231, hash 757E6012
- sample 34:
- time = 789446
- flags = 1
- data = length 223, hash 4003D791
- sample 35:
- time = 812665
- flags = 1
- data = length 232, hash 3CF9A07C
- sample 36:
- time = 835884
- flags = 1
- data = length 228, hash 25AC3FF7
- sample 37:
- time = 859103
- flags = 1
- data = length 220, hash 2C1824CE
- sample 38:
- time = 882322
- flags = 1
- data = length 229, hash 46FDD8FB
- sample 39:
- time = 905541
- flags = 1
- data = length 237, hash F6988018
- sample 40:
- time = 928760
- flags = 1
- data = length 242, hash 60436B6B
- sample 41:
- time = 951979
- flags = 1
- data = length 275, hash 90EDFA8E
- sample 42:
- time = 975198
- flags = 1
- data = length 242, hash 5C86EFCB
- sample 43:
- time = 998417
- flags = 1
- data = length 233, hash E0A51B82
- sample 44:
- time = 1021636
- flags = 1
- data = length 235, hash 590DF14F
- sample 45:
- time = 1044855
- flags = 1
- data = length 238, hash 69AF4E6E
- sample 46:
- time = 1068074
- flags = 1
- data = length 235, hash E745AE8D
- sample 47:
- time = 1091293
- flags = 1
- data = length 223, hash 295F2A13
- sample 48:
- time = 1114512
- flags = 1
- data = length 228, hash E2F47B21
- sample 49:
- time = 1137731
- flags = 1
- data = length 229, hash 262C3CFE
- sample 50:
- time = 1160950
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 51:
- time = 1184169
- flags = 1
- data = length 233, hash F3D80836
- sample 52:
- time = 1207388
- flags = 1
- data = length 237, hash 32E0A11E
- sample 53:
- time = 1230607
- flags = 1
- data = length 228, hash E1B89F13
- sample 54:
- time = 1253826
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 55:
- time = 1277045
- flags = 1
- data = length 235, hash 3C84161F
- sample 56:
- time = 1300264
- flags = 1
- data = length 227, hash A47E1789
- sample 57:
- time = 1323483
- flags = 1
- data = length 228, hash 869FDFD3
- sample 58:
- time = 1346702
- flags = 1
- data = length 233, hash 272ECE2
- sample 59:
- time = 1369921
- flags = 1
- data = length 227, hash DB6B9618
- sample 60:
- time = 1393140
- flags = 1
- data = length 212, hash 63214325
- sample 61:
- time = 1416359
- flags = 1
- data = length 221, hash 9BA588A1
- sample 62:
- time = 1439578
- flags = 1
- data = length 225, hash 21EFD50C
- sample 63:
- time = 1462797
- flags = 1
- data = length 231, hash F3AD0BF
- sample 64:
- time = 1486016
- flags = 1
- data = length 224, hash 822C9210
- sample 65:
- time = 1509235
- flags = 1
- data = length 195, hash D4EF53EE
- sample 66:
- time = 1532454
- flags = 1
- data = length 195, hash A816647A
- sample 67:
- time = 1555673
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 68:
- time = 1578892
- flags = 1
- data = length 210, hash 956E3600
- sample 69:
- time = 1602111
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 70:
- time = 1625330
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 71:
- time = 1648549
- flags = 1
- data = length 228, hash F3B70641
- sample 72:
- time = 1671768
- flags = 1
- data = length 237, hash 124E3194
- sample 73:
- time = 1694987
- flags = 1
- data = length 231, hash 950CD7C8
- sample 74:
- time = 1718206
- flags = 1
- data = length 236, hash A12E49AF
- sample 75:
- time = 1741425
- flags = 1
- data = length 242, hash 43BC9C24
- sample 76:
- time = 1764644
- flags = 1
- data = length 241, hash DCF0B17
- sample 77:
- time = 1787863
- flags = 1
- data = length 251, hash C0B99968
- sample 78:
- time = 1811082
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 79:
- time = 1834301
- flags = 1
- data = length 238, hash 1BA69079
- sample 80:
- time = 1857520
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 81:
- time = 1880739
- flags = 1
- data = length 231, hash EABBEE02
- sample 82:
- time = 1903958
- flags = 1
- data = length 226, hash D09C44FB
- sample 83:
- time = 1927177
- flags = 1
- data = length 235, hash BE6A6608
- sample 84:
- time = 1950396
- flags = 1
- data = length 235, hash 2735F454
- sample 85:
- time = 1973615
- flags = 1
- data = length 238, hash B160DFE7
- sample 86:
- time = 1996834
- flags = 1
- data = length 232, hash 1B217D2E
- sample 87:
- time = 2020053
- flags = 1
- data = length 251, hash D1C14CEA
- sample 88:
- time = 2043272
- flags = 1
- data = length 256, hash 97C87F08
- sample 89:
- time = 2066491
- flags = 1
- data = length 237, hash 6645DB3
- sample 90:
- time = 2089710
- flags = 1
- data = length 235, hash 727A1C82
- sample 91:
- time = 2112929
- flags = 1
- data = length 234, hash 5015F8B5
- sample 92:
- time = 2136148
- flags = 1
- data = length 241, hash 9102144B
- sample 93:
- time = 2159367
- flags = 1
- data = length 224, hash 64E0D807
- sample 94:
- time = 2182586
- flags = 1
- data = length 228, hash 1922B852
- sample 95:
- time = 2205805
- flags = 1
- data = length 224, hash 953502D8
- sample 96:
- time = 2229024
- flags = 1
- data = length 214, hash 92B87FE7
- sample 97:
- time = 2252243
- flags = 1
- data = length 213, hash BB0C8D86
- sample 98:
- time = 2275462
- flags = 1
- data = length 206, hash 9AD21017
- sample 99:
- time = 2298681
- flags = 1
- data = length 209, hash C479FE94
- sample 100:
- time = 2321900
- flags = 1
- data = length 220, hash 3033DCE1
- sample 101:
- time = 2345119
- flags = 1
- data = length 217, hash 7D589C94
- sample 102:
- time = 2368338
- flags = 1
- data = length 216, hash AAF6C183
- sample 103:
- time = 2391557
- flags = 1
- data = length 206, hash 1EE1207F
- sample 104:
- time = 2414776
- flags = 1
- data = length 204, hash 4BEB1210
- sample 105:
- time = 2437995
- flags = 1
- data = length 213, hash 21A841C9
- sample 106:
- time = 2461214
- flags = 1
- data = length 207, hash B80B0424
- sample 107:
- time = 2484433
- flags = 1
- data = length 212, hash 4785A1C3
- sample 108:
- time = 2507652
- flags = 1
- data = length 205, hash 59BF7229
- sample 109:
- time = 2530871
- flags = 1
- data = length 208, hash FA313DDE
- sample 110:
- time = 2554090
- flags = 1
- data = length 211, hash 190D85FD
- sample 111:
- time = 2577309
- flags = 1
- data = length 211, hash BA050052
- sample 112:
- time = 2600528
- flags = 1
- data = length 211, hash F3080F10
- sample 113:
- time = 2623747
- flags = 1
- data = length 210, hash F41B7BE7
- sample 114:
- time = 2646966
- flags = 1
- data = length 207, hash 2176C97E
- sample 115:
- time = 2670185
- flags = 1
- data = length 220, hash 32087455
- sample 116:
- time = 2693404
- flags = 1
- data = length 213, hash 4E5649A8
- sample 117:
- time = 2716623
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 118:
- time = 2739842
- flags = 1
- data = length 204, hash 1E895C2A
- sample 119:
- time = 2763061
- flags = 1
- data = length 219, hash 45382270
- sample 120:
- time = 2786280
- flags = 1
- data = length 205, hash D66C6A1D
- sample 121:
- time = 2809499
- flags = 1
- data = length 204, hash 467AD01F
- sample 122:
- time = 2832718
- flags = 1
- data = length 211, hash F0435574
- sample 123:
- time = 2855937
- flags = 1
- data = length 206, hash 8C96B75F
- sample 124:
- time = 2879156
- flags = 1
- data = length 200, hash 82553248
- sample 125:
- time = 2902375
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 126:
- time = 2925594
- flags = 1
- data = length 196, hash 33151DC4
- sample 127:
- time = 2948813
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 128:
- time = 2972032
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 129:
- time = 2995251
- flags = 1
- data = length 209, hash A72FABAA
- sample 130:
- time = 3018470
- flags = 1
- data = length 217, hash BA33B985
- sample 131:
- time = 3041689
- flags = 1
- data = length 235, hash 9919CFD9
- sample 132:
- time = 3064908
- flags = 1
- data = length 236, hash A22C7267
- sample 133:
- time = 3088127
- flags = 1
- data = length 213, hash 3D57C901
- sample 134:
- time = 3111346
- flags = 1
- data = length 205, hash 47F68FDE
- sample 135:
- time = 3134565
- flags = 1
- data = length 210, hash 9A756E9C
- sample 136:
- time = 3157784
- flags = 1
- data = length 210, hash BD45C31F
- sample 137:
- time = 3181003
- flags = 1
- data = length 207, hash 8774FF7B
- sample 138:
- time = 3204222
- flags = 1
- data = length 149, hash 4678C0E5
- sample 139:
- time = 3227441
- flags = 1
- data = length 161, hash E991035D
- sample 140:
- time = 3250660
- flags = 1
- data = length 197, hash C3013689
- sample 141:
- time = 3273879
- flags = 1
- data = length 208, hash E6C0237
- sample 142:
- time = 3297098
- flags = 1
- data = length 232, hash A330F188
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.1.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.1.dump
deleted file mode 100644
index 8fa122d..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.1.dump
+++ /dev/null
@@ -1,429 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3355717
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 20523
- sample count = 93
- sample 0:
- time = 1118572
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 1:
- time = 1141791
- flags = 1
- data = length 233, hash F3D80836
- sample 2:
- time = 1165010
- flags = 1
- data = length 237, hash 32E0A11E
- sample 3:
- time = 1188229
- flags = 1
- data = length 228, hash E1B89F13
- sample 4:
- time = 1211448
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 5:
- time = 1234667
- flags = 1
- data = length 235, hash 3C84161F
- sample 6:
- time = 1257886
- flags = 1
- data = length 227, hash A47E1789
- sample 7:
- time = 1281105
- flags = 1
- data = length 228, hash 869FDFD3
- sample 8:
- time = 1304324
- flags = 1
- data = length 233, hash 272ECE2
- sample 9:
- time = 1327543
- flags = 1
- data = length 227, hash DB6B9618
- sample 10:
- time = 1350762
- flags = 1
- data = length 212, hash 63214325
- sample 11:
- time = 1373981
- flags = 1
- data = length 221, hash 9BA588A1
- sample 12:
- time = 1397200
- flags = 1
- data = length 225, hash 21EFD50C
- sample 13:
- time = 1420419
- flags = 1
- data = length 231, hash F3AD0BF
- sample 14:
- time = 1443638
- flags = 1
- data = length 224, hash 822C9210
- sample 15:
- time = 1466857
- flags = 1
- data = length 195, hash D4EF53EE
- sample 16:
- time = 1490076
- flags = 1
- data = length 195, hash A816647A
- sample 17:
- time = 1513295
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 18:
- time = 1536514
- flags = 1
- data = length 210, hash 956E3600
- sample 19:
- time = 1559733
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 20:
- time = 1582952
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 21:
- time = 1606171
- flags = 1
- data = length 228, hash F3B70641
- sample 22:
- time = 1629390
- flags = 1
- data = length 237, hash 124E3194
- sample 23:
- time = 1652609
- flags = 1
- data = length 231, hash 950CD7C8
- sample 24:
- time = 1675828
- flags = 1
- data = length 236, hash A12E49AF
- sample 25:
- time = 1699047
- flags = 1
- data = length 242, hash 43BC9C24
- sample 26:
- time = 1722266
- flags = 1
- data = length 241, hash DCF0B17
- sample 27:
- time = 1745485
- flags = 1
- data = length 251, hash C0B99968
- sample 28:
- time = 1768704
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 29:
- time = 1791923
- flags = 1
- data = length 238, hash 1BA69079
- sample 30:
- time = 1815142
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 31:
- time = 1838361
- flags = 1
- data = length 231, hash EABBEE02
- sample 32:
- time = 1861580
- flags = 1
- data = length 226, hash D09C44FB
- sample 33:
- time = 1884799
- flags = 1
- data = length 235, hash BE6A6608
- sample 34:
- time = 1908018
- flags = 1
- data = length 235, hash 2735F454
- sample 35:
- time = 1931237
- flags = 1
- data = length 238, hash B160DFE7
- sample 36:
- time = 1954456
- flags = 1
- data = length 232, hash 1B217D2E
- sample 37:
- time = 1977675
- flags = 1
- data = length 251, hash D1C14CEA
- sample 38:
- time = 2000894
- flags = 1
- data = length 256, hash 97C87F08
- sample 39:
- time = 2024113
- flags = 1
- data = length 237, hash 6645DB3
- sample 40:
- time = 2047332
- flags = 1
- data = length 235, hash 727A1C82
- sample 41:
- time = 2070551
- flags = 1
- data = length 234, hash 5015F8B5
- sample 42:
- time = 2093770
- flags = 1
- data = length 241, hash 9102144B
- sample 43:
- time = 2116989
- flags = 1
- data = length 224, hash 64E0D807
- sample 44:
- time = 2140208
- flags = 1
- data = length 228, hash 1922B852
- sample 45:
- time = 2163427
- flags = 1
- data = length 224, hash 953502D8
- sample 46:
- time = 2186646
- flags = 1
- data = length 214, hash 92B87FE7
- sample 47:
- time = 2209865
- flags = 1
- data = length 213, hash BB0C8D86
- sample 48:
- time = 2233084
- flags = 1
- data = length 206, hash 9AD21017
- sample 49:
- time = 2256303
- flags = 1
- data = length 209, hash C479FE94
- sample 50:
- time = 2279522
- flags = 1
- data = length 220, hash 3033DCE1
- sample 51:
- time = 2302741
- flags = 1
- data = length 217, hash 7D589C94
- sample 52:
- time = 2325960
- flags = 1
- data = length 216, hash AAF6C183
- sample 53:
- time = 2349179
- flags = 1
- data = length 206, hash 1EE1207F
- sample 54:
- time = 2372398
- flags = 1
- data = length 204, hash 4BEB1210
- sample 55:
- time = 2395617
- flags = 1
- data = length 213, hash 21A841C9
- sample 56:
- time = 2418836
- flags = 1
- data = length 207, hash B80B0424
- sample 57:
- time = 2442055
- flags = 1
- data = length 212, hash 4785A1C3
- sample 58:
- time = 2465274
- flags = 1
- data = length 205, hash 59BF7229
- sample 59:
- time = 2488493
- flags = 1
- data = length 208, hash FA313DDE
- sample 60:
- time = 2511712
- flags = 1
- data = length 211, hash 190D85FD
- sample 61:
- time = 2534931
- flags = 1
- data = length 211, hash BA050052
- sample 62:
- time = 2558150
- flags = 1
- data = length 211, hash F3080F10
- sample 63:
- time = 2581369
- flags = 1
- data = length 210, hash F41B7BE7
- sample 64:
- time = 2604588
- flags = 1
- data = length 207, hash 2176C97E
- sample 65:
- time = 2627807
- flags = 1
- data = length 220, hash 32087455
- sample 66:
- time = 2651026
- flags = 1
- data = length 213, hash 4E5649A8
- sample 67:
- time = 2674245
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 68:
- time = 2697464
- flags = 1
- data = length 204, hash 1E895C2A
- sample 69:
- time = 2720683
- flags = 1
- data = length 219, hash 45382270
- sample 70:
- time = 2743902
- flags = 1
- data = length 205, hash D66C6A1D
- sample 71:
- time = 2767121
- flags = 1
- data = length 204, hash 467AD01F
- sample 72:
- time = 2790340
- flags = 1
- data = length 211, hash F0435574
- sample 73:
- time = 2813559
- flags = 1
- data = length 206, hash 8C96B75F
- sample 74:
- time = 2836778
- flags = 1
- data = length 200, hash 82553248
- sample 75:
- time = 2859997
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 76:
- time = 2883216
- flags = 1
- data = length 196, hash 33151DC4
- sample 77:
- time = 2906435
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 78:
- time = 2929654
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 79:
- time = 2952873
- flags = 1
- data = length 209, hash A72FABAA
- sample 80:
- time = 2976092
- flags = 1
- data = length 217, hash BA33B985
- sample 81:
- time = 2999311
- flags = 1
- data = length 235, hash 9919CFD9
- sample 82:
- time = 3022530
- flags = 1
- data = length 236, hash A22C7267
- sample 83:
- time = 3045749
- flags = 1
- data = length 213, hash 3D57C901
- sample 84:
- time = 3068968
- flags = 1
- data = length 205, hash 47F68FDE
- sample 85:
- time = 3092187
- flags = 1
- data = length 210, hash 9A756E9C
- sample 86:
- time = 3115406
- flags = 1
- data = length 210, hash BD45C31F
- sample 87:
- time = 3138625
- flags = 1
- data = length 207, hash 8774FF7B
- sample 88:
- time = 3161844
- flags = 1
- data = length 149, hash 4678C0E5
- sample 89:
- time = 3185063
- flags = 1
- data = length 161, hash E991035D
- sample 90:
- time = 3208282
- flags = 1
- data = length 197, hash C3013689
- sample 91:
- time = 3231501
- flags = 1
- data = length 208, hash E6C0237
- sample 92:
- time = 3254720
- flags = 1
- data = length 232, hash A330F188
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.2.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.2.dump
deleted file mode 100644
index 512245a..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.2.dump
+++ /dev/null
@@ -1,249 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3355717
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 10151
- sample count = 48
- sample 0:
- time = 2237144
- flags = 1
- data = length 224, hash 953502D8
- sample 1:
- time = 2260363
- flags = 1
- data = length 214, hash 92B87FE7
- sample 2:
- time = 2283582
- flags = 1
- data = length 213, hash BB0C8D86
- sample 3:
- time = 2306801
- flags = 1
- data = length 206, hash 9AD21017
- sample 4:
- time = 2330020
- flags = 1
- data = length 209, hash C479FE94
- sample 5:
- time = 2353239
- flags = 1
- data = length 220, hash 3033DCE1
- sample 6:
- time = 2376458
- flags = 1
- data = length 217, hash 7D589C94
- sample 7:
- time = 2399677
- flags = 1
- data = length 216, hash AAF6C183
- sample 8:
- time = 2422896
- flags = 1
- data = length 206, hash 1EE1207F
- sample 9:
- time = 2446115
- flags = 1
- data = length 204, hash 4BEB1210
- sample 10:
- time = 2469334
- flags = 1
- data = length 213, hash 21A841C9
- sample 11:
- time = 2492553
- flags = 1
- data = length 207, hash B80B0424
- sample 12:
- time = 2515772
- flags = 1
- data = length 212, hash 4785A1C3
- sample 13:
- time = 2538991
- flags = 1
- data = length 205, hash 59BF7229
- sample 14:
- time = 2562210
- flags = 1
- data = length 208, hash FA313DDE
- sample 15:
- time = 2585429
- flags = 1
- data = length 211, hash 190D85FD
- sample 16:
- time = 2608648
- flags = 1
- data = length 211, hash BA050052
- sample 17:
- time = 2631867
- flags = 1
- data = length 211, hash F3080F10
- sample 18:
- time = 2655086
- flags = 1
- data = length 210, hash F41B7BE7
- sample 19:
- time = 2678305
- flags = 1
- data = length 207, hash 2176C97E
- sample 20:
- time = 2701524
- flags = 1
- data = length 220, hash 32087455
- sample 21:
- time = 2724743
- flags = 1
- data = length 213, hash 4E5649A8
- sample 22:
- time = 2747962
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 23:
- time = 2771181
- flags = 1
- data = length 204, hash 1E895C2A
- sample 24:
- time = 2794400
- flags = 1
- data = length 219, hash 45382270
- sample 25:
- time = 2817619
- flags = 1
- data = length 205, hash D66C6A1D
- sample 26:
- time = 2840838
- flags = 1
- data = length 204, hash 467AD01F
- sample 27:
- time = 2864057
- flags = 1
- data = length 211, hash F0435574
- sample 28:
- time = 2887276
- flags = 1
- data = length 206, hash 8C96B75F
- sample 29:
- time = 2910495
- flags = 1
- data = length 200, hash 82553248
- sample 30:
- time = 2933714
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 31:
- time = 2956933
- flags = 1
- data = length 196, hash 33151DC4
- sample 32:
- time = 2980152
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 33:
- time = 3003371
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 34:
- time = 3026590
- flags = 1
- data = length 209, hash A72FABAA
- sample 35:
- time = 3049809
- flags = 1
- data = length 217, hash BA33B985
- sample 36:
- time = 3073028
- flags = 1
- data = length 235, hash 9919CFD9
- sample 37:
- time = 3096247
- flags = 1
- data = length 236, hash A22C7267
- sample 38:
- time = 3119466
- flags = 1
- data = length 213, hash 3D57C901
- sample 39:
- time = 3142685
- flags = 1
- data = length 205, hash 47F68FDE
- sample 40:
- time = 3165904
- flags = 1
- data = length 210, hash 9A756E9C
- sample 41:
- time = 3189123
- flags = 1
- data = length 210, hash BD45C31F
- sample 42:
- time = 3212342
- flags = 1
- data = length 207, hash 8774FF7B
- sample 43:
- time = 3235561
- flags = 1
- data = length 149, hash 4678C0E5
- sample 44:
- time = 3258780
- flags = 1
- data = length 161, hash E991035D
- sample 45:
- time = 3281999
- flags = 1
- data = length 197, hash C3013689
- sample 46:
- time = 3305218
- flags = 1
- data = length 208, hash E6C0237
- sample 47:
- time = 3328437
- flags = 1
- data = length 232, hash A330F188
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.3.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.3.dump
deleted file mode 100644
index 7168ef4..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.3.dump
+++ /dev/null
@@ -1,57 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 3355717
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 164
- sample count = 0
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.unklen.dump b/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.unklen.dump
deleted file mode 100644
index 8d427ba..0000000
--- a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts.unklen.dump
+++ /dev/null
@@ -1,629 +0,0 @@
-seekMap:
- isSeekable = false
- duration = UNSET TIME
- getPosition(0) = [[timeUs=0, position=0]]
-numberOfTracks = 2
-track 0:
- format:
- bitrate = -1
- id = 0
- containerMimeType = null
- sampleMimeType = audio/mp4a-latm
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- data = length 2, hash 5F7
- total output bytes = 30787
- sample count = 143
- sample 0:
- time = 0
- flags = 1
- data = length 23, hash 47DE9131
- sample 1:
- time = 23219
- flags = 1
- data = length 6, hash 31CF3A46
- sample 2:
- time = 46438
- flags = 1
- data = length 6, hash 31CF3A46
- sample 3:
- time = 69657
- flags = 1
- data = length 6, hash 31CF3A46
- sample 4:
- time = 92876
- flags = 1
- data = length 6, hash 31EC5206
- sample 5:
- time = 116095
- flags = 1
- data = length 171, hash 4F6478F6
- sample 6:
- time = 139314
- flags = 1
- data = length 202, hash AF4068A3
- sample 7:
- time = 162533
- flags = 1
- data = length 210, hash E4C10618
- sample 8:
- time = 185752
- flags = 1
- data = length 217, hash 9ECCD0D9
- sample 9:
- time = 208971
- flags = 1
- data = length 212, hash 6BAC2CD9
- sample 10:
- time = 232190
- flags = 1
- data = length 223, hash 188B6010
- sample 11:
- time = 255409
- flags = 1
- data = length 222, hash C1A04D0C
- sample 12:
- time = 278628
- flags = 1
- data = length 220, hash D65F9768
- sample 13:
- time = 301847
- flags = 1
- data = length 227, hash B96C9E14
- sample 14:
- time = 325066
- flags = 1
- data = length 229, hash 9FB09972
- sample 15:
- time = 348285
- flags = 1
- data = length 220, hash 2271F053
- sample 16:
- time = 371504
- flags = 1
- data = length 226, hash 5EDD2F4F
- sample 17:
- time = 394723
- flags = 1
- data = length 239, hash 957510E0
- sample 18:
- time = 417942
- flags = 1
- data = length 224, hash 718A8F47
- sample 19:
- time = 441161
- flags = 1
- data = length 225, hash 5E11E293
- sample 20:
- time = 464380
- flags = 1
- data = length 227, hash FCE50D27
- sample 21:
- time = 487599
- flags = 1
- data = length 212, hash 77908C40
- sample 22:
- time = 510818
- flags = 1
- data = length 227, hash 34C4EB32
- sample 23:
- time = 534037
- flags = 1
- data = length 231, hash 95488307
- sample 24:
- time = 557256
- flags = 1
- data = length 226, hash 97F12D6F
- sample 25:
- time = 580475
- flags = 1
- data = length 236, hash 91A9D9A2
- sample 26:
- time = 603694
- flags = 1
- data = length 227, hash 27A608F9
- sample 27:
- time = 626913
- flags = 1
- data = length 229, hash 57DAAE4
- sample 28:
- time = 650132
- flags = 1
- data = length 235, hash ED30AC34
- sample 29:
- time = 673351
- flags = 1
- data = length 227, hash BD3D6280
- sample 30:
- time = 696570
- flags = 1
- data = length 233, hash 694B1087
- sample 31:
- time = 719789
- flags = 1
- data = length 232, hash 1EDFE047
- sample 32:
- time = 743008
- flags = 1
- data = length 228, hash E2A831F4
- sample 33:
- time = 766227
- flags = 1
- data = length 231, hash 757E6012
- sample 34:
- time = 789446
- flags = 1
- data = length 223, hash 4003D791
- sample 35:
- time = 812665
- flags = 1
- data = length 232, hash 3CF9A07C
- sample 36:
- time = 835884
- flags = 1
- data = length 228, hash 25AC3FF7
- sample 37:
- time = 859103
- flags = 1
- data = length 220, hash 2C1824CE
- sample 38:
- time = 882322
- flags = 1
- data = length 229, hash 46FDD8FB
- sample 39:
- time = 905541
- flags = 1
- data = length 237, hash F6988018
- sample 40:
- time = 928760
- flags = 1
- data = length 242, hash 60436B6B
- sample 41:
- time = 951979
- flags = 1
- data = length 275, hash 90EDFA8E
- sample 42:
- time = 975198
- flags = 1
- data = length 242, hash 5C86EFCB
- sample 43:
- time = 998417
- flags = 1
- data = length 233, hash E0A51B82
- sample 44:
- time = 1021636
- flags = 1
- data = length 235, hash 590DF14F
- sample 45:
- time = 1044855
- flags = 1
- data = length 238, hash 69AF4E6E
- sample 46:
- time = 1068074
- flags = 1
- data = length 235, hash E745AE8D
- sample 47:
- time = 1091293
- flags = 1
- data = length 223, hash 295F2A13
- sample 48:
- time = 1114512
- flags = 1
- data = length 228, hash E2F47B21
- sample 49:
- time = 1137731
- flags = 1
- data = length 229, hash 262C3CFE
- sample 50:
- time = 1160950
- flags = 1
- data = length 232, hash 4B5BF5E8
- sample 51:
- time = 1184169
- flags = 1
- data = length 233, hash F3D80836
- sample 52:
- time = 1207388
- flags = 1
- data = length 237, hash 32E0A11E
- sample 53:
- time = 1230607
- flags = 1
- data = length 228, hash E1B89F13
- sample 54:
- time = 1253826
- flags = 1
- data = length 237, hash 8BDD9E38
- sample 55:
- time = 1277045
- flags = 1
- data = length 235, hash 3C84161F
- sample 56:
- time = 1300264
- flags = 1
- data = length 227, hash A47E1789
- sample 57:
- time = 1323483
- flags = 1
- data = length 228, hash 869FDFD3
- sample 58:
- time = 1346702
- flags = 1
- data = length 233, hash 272ECE2
- sample 59:
- time = 1369921
- flags = 1
- data = length 227, hash DB6B9618
- sample 60:
- time = 1393140
- flags = 1
- data = length 212, hash 63214325
- sample 61:
- time = 1416359
- flags = 1
- data = length 221, hash 9BA588A1
- sample 62:
- time = 1439578
- flags = 1
- data = length 225, hash 21EFD50C
- sample 63:
- time = 1462797
- flags = 1
- data = length 231, hash F3AD0BF
- sample 64:
- time = 1486016
- flags = 1
- data = length 224, hash 822C9210
- sample 65:
- time = 1509235
- flags = 1
- data = length 195, hash D4EF53EE
- sample 66:
- time = 1532454
- flags = 1
- data = length 195, hash A816647A
- sample 67:
- time = 1555673
- flags = 1
- data = length 184, hash 9A2B7E6
- sample 68:
- time = 1578892
- flags = 1
- data = length 210, hash 956E3600
- sample 69:
- time = 1602111
- flags = 1
- data = length 234, hash 35CFDA0A
- sample 70:
- time = 1625330
- flags = 1
- data = length 239, hash 9E15AC1E
- sample 71:
- time = 1648549
- flags = 1
- data = length 228, hash F3B70641
- sample 72:
- time = 1671768
- flags = 1
- data = length 237, hash 124E3194
- sample 73:
- time = 1694987
- flags = 1
- data = length 231, hash 950CD7C8
- sample 74:
- time = 1718206
- flags = 1
- data = length 236, hash A12E49AF
- sample 75:
- time = 1741425
- flags = 1
- data = length 242, hash 43BC9C24
- sample 76:
- time = 1764644
- flags = 1
- data = length 241, hash DCF0B17
- sample 77:
- time = 1787863
- flags = 1
- data = length 251, hash C0B99968
- sample 78:
- time = 1811082
- flags = 1
- data = length 245, hash 9B38ED1C
- sample 79:
- time = 1834301
- flags = 1
- data = length 238, hash 1BA69079
- sample 80:
- time = 1857520
- flags = 1
- data = length 233, hash 44C8C6BF
- sample 81:
- time = 1880739
- flags = 1
- data = length 231, hash EABBEE02
- sample 82:
- time = 1903958
- flags = 1
- data = length 226, hash D09C44FB
- sample 83:
- time = 1927177
- flags = 1
- data = length 235, hash BE6A6608
- sample 84:
- time = 1950396
- flags = 1
- data = length 235, hash 2735F454
- sample 85:
- time = 1973615
- flags = 1
- data = length 238, hash B160DFE7
- sample 86:
- time = 1996834
- flags = 1
- data = length 232, hash 1B217D2E
- sample 87:
- time = 2020053
- flags = 1
- data = length 251, hash D1C14CEA
- sample 88:
- time = 2043272
- flags = 1
- data = length 256, hash 97C87F08
- sample 89:
- time = 2066491
- flags = 1
- data = length 237, hash 6645DB3
- sample 90:
- time = 2089710
- flags = 1
- data = length 235, hash 727A1C82
- sample 91:
- time = 2112929
- flags = 1
- data = length 234, hash 5015F8B5
- sample 92:
- time = 2136148
- flags = 1
- data = length 241, hash 9102144B
- sample 93:
- time = 2159367
- flags = 1
- data = length 224, hash 64E0D807
- sample 94:
- time = 2182586
- flags = 1
- data = length 228, hash 1922B852
- sample 95:
- time = 2205805
- flags = 1
- data = length 224, hash 953502D8
- sample 96:
- time = 2229024
- flags = 1
- data = length 214, hash 92B87FE7
- sample 97:
- time = 2252243
- flags = 1
- data = length 213, hash BB0C8D86
- sample 98:
- time = 2275462
- flags = 1
- data = length 206, hash 9AD21017
- sample 99:
- time = 2298681
- flags = 1
- data = length 209, hash C479FE94
- sample 100:
- time = 2321900
- flags = 1
- data = length 220, hash 3033DCE1
- sample 101:
- time = 2345119
- flags = 1
- data = length 217, hash 7D589C94
- sample 102:
- time = 2368338
- flags = 1
- data = length 216, hash AAF6C183
- sample 103:
- time = 2391557
- flags = 1
- data = length 206, hash 1EE1207F
- sample 104:
- time = 2414776
- flags = 1
- data = length 204, hash 4BEB1210
- sample 105:
- time = 2437995
- flags = 1
- data = length 213, hash 21A841C9
- sample 106:
- time = 2461214
- flags = 1
- data = length 207, hash B80B0424
- sample 107:
- time = 2484433
- flags = 1
- data = length 212, hash 4785A1C3
- sample 108:
- time = 2507652
- flags = 1
- data = length 205, hash 59BF7229
- sample 109:
- time = 2530871
- flags = 1
- data = length 208, hash FA313DDE
- sample 110:
- time = 2554090
- flags = 1
- data = length 211, hash 190D85FD
- sample 111:
- time = 2577309
- flags = 1
- data = length 211, hash BA050052
- sample 112:
- time = 2600528
- flags = 1
- data = length 211, hash F3080F10
- sample 113:
- time = 2623747
- flags = 1
- data = length 210, hash F41B7BE7
- sample 114:
- time = 2646966
- flags = 1
- data = length 207, hash 2176C97E
- sample 115:
- time = 2670185
- flags = 1
- data = length 220, hash 32087455
- sample 116:
- time = 2693404
- flags = 1
- data = length 213, hash 4E5649A8
- sample 117:
- time = 2716623
- flags = 1
- data = length 213, hash 5F12FDCF
- sample 118:
- time = 2739842
- flags = 1
- data = length 204, hash 1E895C2A
- sample 119:
- time = 2763061
- flags = 1
- data = length 219, hash 45382270
- sample 120:
- time = 2786280
- flags = 1
- data = length 205, hash D66C6A1D
- sample 121:
- time = 2809499
- flags = 1
- data = length 204, hash 467AD01F
- sample 122:
- time = 2832718
- flags = 1
- data = length 211, hash F0435574
- sample 123:
- time = 2855937
- flags = 1
- data = length 206, hash 8C96B75F
- sample 124:
- time = 2879156
- flags = 1
- data = length 200, hash 82553248
- sample 125:
- time = 2902375
- flags = 1
- data = length 180, hash 1E51E6CE
- sample 126:
- time = 2925594
- flags = 1
- data = length 196, hash 33151DC4
- sample 127:
- time = 2948813
- flags = 1
- data = length 197, hash 1E62A7D6
- sample 128:
- time = 2972032
- flags = 1
- data = length 206, hash 6A6C4CC9
- sample 129:
- time = 2995251
- flags = 1
- data = length 209, hash A72FABAA
- sample 130:
- time = 3018470
- flags = 1
- data = length 217, hash BA33B985
- sample 131:
- time = 3041689
- flags = 1
- data = length 235, hash 9919CFD9
- sample 132:
- time = 3064908
- flags = 1
- data = length 236, hash A22C7267
- sample 133:
- time = 3088127
- flags = 1
- data = length 213, hash 3D57C901
- sample 134:
- time = 3111346
- flags = 1
- data = length 205, hash 47F68FDE
- sample 135:
- time = 3134565
- flags = 1
- data = length 210, hash 9A756E9C
- sample 136:
- time = 3157784
- flags = 1
- data = length 210, hash BD45C31F
- sample 137:
- time = 3181003
- flags = 1
- data = length 207, hash 8774FF7B
- sample 138:
- time = 3204222
- flags = 1
- data = length 149, hash 4678C0E5
- sample 139:
- time = 3227441
- flags = 1
- data = length 161, hash E991035D
- sample 140:
- time = 3250660
- flags = 1
- data = length 197, hash C3013689
- sample 141:
- time = 3273879
- flags = 1
- data = length 208, hash E6C0237
- sample 142:
- time = 3297098
- flags = 1
- data = length 232, hash A330F188
-track 1:
- format:
- bitrate = -1
- id = 1
- containerMimeType = null
- sampleMimeType = application/id3
- maxInputSize = -1
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = -1
- sampleRate = -1
- pcmEncoding = -1
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 0
- sample count = 0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample.wav.0.dump b/tree/library/extractor/src/test/assets/wav/sample.wav.0.dump
deleted file mode 100644
index 50daee0..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample.wav.0.dump
+++ /dev/null
@@ -1,71 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1000000
- getPosition(0) = [[timeUs=0, position=78]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 705600
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 88200
- sample count = 10
- sample 0:
- time = 0
- flags = 1
- data = length 8820, hash FAE27E28
- sample 1:
- time = 100000
- flags = 1
- data = length 8820, hash 21C3E9C3
- sample 2:
- time = 200000
- flags = 1
- data = length 8820, hash B51AD902
- sample 3:
- time = 300000
- flags = 1
- data = length 8820, hash 2F4B2CB4
- sample 4:
- time = 400000
- flags = 1
- data = length 8820, hash F0030CC2
- sample 5:
- time = 500000
- flags = 1
- data = length 8820, hash FF83DA46
- sample 6:
- time = 600000
- flags = 1
- data = length 8820, hash 685C1AB5
- sample 7:
- time = 700000
- flags = 1
- data = length 8820, hash BE63D51C
- sample 8:
- time = 800000
- flags = 1
- data = length 8820, hash 1E44EB8E
- sample 9:
- time = 900000
- flags = 1
- data = length 8820, hash 57C41232
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample.wav.1.dump b/tree/library/extractor/src/test/assets/wav/sample.wav.1.dump
deleted file mode 100644
index 80fa9c0..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample.wav.1.dump
+++ /dev/null
@@ -1,59 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1000000
- getPosition(0) = [[timeUs=0, position=78]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 705600
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 58802
- sample count = 7
- sample 0:
- time = 333333
- flags = 1
- data = length 8820, hash 31868A21
- sample 1:
- time = 433333
- flags = 1
- data = length 8820, hash AE3D77A2
- sample 2:
- time = 533333
- flags = 1
- data = length 8820, hash 966140CE
- sample 3:
- time = 633333
- flags = 1
- data = length 8820, hash CB405D7B
- sample 4:
- time = 733333
- flags = 1
- data = length 8820, hash 733BA3E6
- sample 5:
- time = 833333
- flags = 1
- data = length 8820, hash 7595D752
- sample 6:
- time = 933333
- flags = 1
- data = length 5882, hash C617B719
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample.wav.2.dump b/tree/library/extractor/src/test/assets/wav/sample.wav.2.dump
deleted file mode 100644
index e848e81..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample.wav.2.dump
+++ /dev/null
@@ -1,47 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1000000
- getPosition(0) = [[timeUs=0, position=78]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 705600
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 29402
- sample count = 4
- sample 0:
- time = 666666
- flags = 1
- data = length 8820, hash D6617E20
- sample 1:
- time = 766666
- flags = 1
- data = length 8820, hash 28C74B7A
- sample 2:
- time = 866666
- flags = 1
- data = length 8820, hash 680DEFC7
- sample 3:
- time = 966666
- flags = 1
- data = length 2942, hash 1D063CF0
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample.wav.3.dump b/tree/library/extractor/src/test/assets/wav/sample.wav.3.dump
deleted file mode 100644
index 3216b1e..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample.wav.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1000000
- getPosition(0) = [[timeUs=0, position=78]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 705600
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 2
- sample count = 1
- sample 0:
- time = 1000000
- flags = 1
- data = length 2, hash 116
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.0.dump b/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.0.dump
deleted file mode 100644
index a16ad68..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.0.dump
+++ /dev/null
@@ -1,75 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1018185
- getPosition(0) = [[timeUs=0, position=94]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 177004
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 89804
- sample count = 11
- sample 0:
- time = 0
- flags = 1
- data = length 8820, hash E90A457C
- sample 1:
- time = 100000
- flags = 1
- data = length 8820, hash EA798370
- sample 2:
- time = 200000
- flags = 1
- data = length 8820, hash A57ED989
- sample 3:
- time = 300000
- flags = 1
- data = length 8820, hash 8B681816
- sample 4:
- time = 400000
- flags = 1
- data = length 8820, hash 48177BEB
- sample 5:
- time = 500000
- flags = 1
- data = length 8820, hash 70197776
- sample 6:
- time = 600000
- flags = 1
- data = length 8820, hash DB4A4704
- sample 7:
- time = 700000
- flags = 1
- data = length 8820, hash 84A525D0
- sample 8:
- time = 800000
- flags = 1
- data = length 8820, hash 197A4377
- sample 9:
- time = 900000
- flags = 1
- data = length 8820, hash 6982BC91
- sample 10:
- time = 1000000
- flags = 1
- data = length 1604, hash 3DED68ED
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.1.dump b/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.1.dump
deleted file mode 100644
index 3eb13e8..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.1.dump
+++ /dev/null
@@ -1,59 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1018185
- getPosition(0) = [[timeUs=0, position=94]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 177004
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 61230
- sample count = 7
- sample 0:
- time = 339395
- flags = 1
- data = length 8820, hash 25FCA092
- sample 1:
- time = 439395
- flags = 1
- data = length 8820, hash 9400B4BE
- sample 2:
- time = 539395
- flags = 1
- data = length 8820, hash 5BA7E45D
- sample 3:
- time = 639395
- flags = 1
- data = length 8820, hash 5AC42905
- sample 4:
- time = 739395
- flags = 1
- data = length 8820, hash D57059C
- sample 5:
- time = 839395
- flags = 1
- data = length 8820, hash DEF5C480
- sample 6:
- time = 939395
- flags = 1
- data = length 8310, hash 10B3FC93
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.2.dump b/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.2.dump
deleted file mode 100644
index bef1652..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.2.dump
+++ /dev/null
@@ -1,47 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1018185
- getPosition(0) = [[timeUs=0, position=94]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 177004
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 32656
- sample count = 4
- sample 0:
- time = 678790
- flags = 1
- data = length 8820, hash DB7FF64C
- sample 1:
- time = 778790
- flags = 1
- data = length 8820, hash B895DFDC
- sample 2:
- time = 878790
- flags = 1
- data = length 8820, hash E3AB416D
- sample 3:
- time = 978790
- flags = 1
- data = length 6196, hash E27E175A
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.3.dump b/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.3.dump
deleted file mode 100644
index 085fe5e..0000000
--- a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav.3.dump
+++ /dev/null
@@ -1,35 +0,0 @@
-seekMap:
- isSeekable = true
- duration = 1018185
- getPosition(0) = [[timeUs=0, position=94]]
-numberOfTracks = 1
-track 0:
- format:
- bitrate = 177004
- id = null
- containerMimeType = null
- sampleMimeType = audio/raw
- maxInputSize = 8820
- width = -1
- height = -1
- frameRate = -1.0
- rotationDegrees = 0
- pixelWidthHeightRatio = 1.0
- channelCount = 1
- sampleRate = 44100
- pcmEncoding = 2
- encoderDelay = 0
- encoderPadding = 0
- subsampleOffsetUs = 9223372036854775807
- selectionFlags = 0
- language = null
- drmInitData = -
- metadata = null
- initializationData:
- total output bytes = 4082
- sample count = 1
- sample 0:
- time = 1018185
- flags = 1
- data = length 4082, hash 4CB1A490
-tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/webm/vorbis_codec_private b/tree/library/extractor/src/test/assets/webm/vorbis_codec_private
deleted file mode 100644
index 6a61344..0000000
--- a/tree/library/extractor/src/test/assets/webm/vorbis_codec_private
+++ /dev/null
Binary files differ
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMapTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMapTest.java
index bc3ccf4..4d70fe3 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMapTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMapTest.java
@@ -30,7 +30,7 @@
private ConstantBitrateSeekMap constantBitrateSeekMap;
@Test
- public void testIsSeekable_forKnownInputLength_returnSeekable() {
+ public void isSeekable_forKnownInputLength_returnSeekable() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 1000,
@@ -41,7 +41,7 @@
}
@Test
- public void testIsSeekable_forUnknownInputLength_returnUnseekable() {
+ public void isSeekable_forUnknownInputLength_returnUnseekable() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ C.LENGTH_UNSET,
@@ -52,7 +52,7 @@
}
@Test
- public void testGetSeekPoints_forUnseekableInput_returnSeekPoint0() {
+ public void getSeekPoints_forUnseekableInput_returnSeekPoint0() {
int firstBytePosition = 100;
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
@@ -67,7 +67,7 @@
}
@Test
- public void testGetDurationUs_forKnownInputLength_returnCorrectDuration() {
+ public void getDurationUs_forKnownInputLength_returnCorrectDuration() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -81,7 +81,7 @@
}
@Test
- public void testGetDurationUs_forUnnnownInputLength_returnUnknownDuration() {
+ public void getDurationUs_forUnnnownInputLength_returnUnknownDuration() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ C.LENGTH_UNSET,
@@ -92,7 +92,7 @@
}
@Test
- public void testGetSeekPoints_forSeekableInput_forSyncPosition0_return1SeekPoint() {
+ public void getSeekPoints_forSeekableInput_forSyncPosition0_return1SeekPoint() {
int firstBytePosition = 100;
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
@@ -107,7 +107,7 @@
}
@Test
- public void testGetSeekPoints_forSeekableInput_forSeekPointAtSyncPosition_return1SeekPoint() {
+ public void getSeekPoints_forSeekableInput_forSeekPointAtSyncPosition_return1SeekPoint() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -123,7 +123,7 @@
}
@Test
- public void testGetSeekPoints_forSeekableInput_forNonSyncSeekPosition_return2SeekPoints() {
+ public void getSeekPoints_forSeekableInput_forNonSyncSeekPosition_return2SeekPoints() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -140,7 +140,7 @@
}
@Test
- public void testGetSeekPoints_forSeekableInput_forSeekPointWithinLastFrame_return1SeekPoint() {
+ public void getSeekPoints_forSeekableInput_forSeekPointWithinLastFrame_return1SeekPoint() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -154,7 +154,7 @@
}
@Test
- public void testGetSeekPoints_forSeekableInput_forSeekPointAtEndOfStream_return1SeekPoint() {
+ public void getSeekPoints_forSeekableInput_forSeekPointAtEndOfStream_return1SeekPoint() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -168,7 +168,7 @@
}
@Test
- public void testGetTimeUsAtPosition_forPosition0_return0() {
+ public void getTimeUsAtPosition_forPosition0_return0() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -180,7 +180,7 @@
}
@Test
- public void testGetTimeUsAtPosition_forPositionWithinStream_returnCorrectTime() {
+ public void getTimeUsAtPosition_forPositionWithinStream_returnCorrectTime() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
@@ -192,7 +192,7 @@
}
@Test
- public void testGetTimeUsAtPosition_forPositionAtEndOfStream_returnStreamDuration() {
+ public void getTimeUsAtPosition_forPositionAtEndOfStream_returnStreamDuration() {
constantBitrateSeekMap =
new ConstantBitrateSeekMap(
/* inputLength= */ 2_300,
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java
index ccc806f..f404f24 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java
@@ -41,7 +41,7 @@
private static final int LARGE_TEST_DATA_LENGTH = 8192;
@Test
- public void testInitialPosition() throws Exception {
+ public void initialPosition() throws Exception {
FakeDataSource testDataSource = buildDataSource();
DefaultExtractorInput input =
new DefaultExtractorInput(testDataSource, 123, C.LENGTH_UNSET);
@@ -49,7 +49,7 @@
}
@Test
- public void testReadMultipleTimes() throws Exception {
+ public void readMultipleTimes() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
// We expect to perform three reads of three bytes, as setup in buildTestDataSource.
@@ -65,7 +65,7 @@
}
@Test
- public void testReadAlreadyPeeked() throws Exception {
+ public void readAlreadyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -79,7 +79,7 @@
}
@Test
- public void testReadPartiallyPeeked() throws Exception {
+ public void readPartiallyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -93,7 +93,7 @@
}
@Test
- public void testReadEndOfInputBeforeFirstByteRead() throws Exception {
+ public void readEndOfInputBeforeFirstByteRead() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -105,7 +105,7 @@
}
@Test
- public void testReadEndOfInputAfterFirstByteRead() throws Exception {
+ public void readEndOfInputAfterFirstByteRead() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -117,7 +117,7 @@
}
@Test
- public void testReadZeroLength() throws Exception {
+ public void readZeroLength() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -127,7 +127,7 @@
}
@Test
- public void testReadFullyOnce() throws Exception {
+ public void readFullyOnce() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
input.readFully(target, 0, TEST_DATA.length);
@@ -147,7 +147,7 @@
}
@Test
- public void testReadFullyTwice() throws Exception {
+ public void readFullyTwice() throws Exception {
// Read TEST_DATA in two parts.
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[5];
@@ -161,7 +161,7 @@
}
@Test
- public void testReadFullyTooMuch() throws Exception {
+ public void readFullyTooMuch() throws Exception {
// Read more than TEST_DATA. Should fail with an EOFException. Position should not update.
DefaultExtractorInput input = createDefaultExtractorInput();
try {
@@ -187,7 +187,7 @@
}
@Test
- public void testReadFullyWithFailingDataSource() throws Exception {
+ public void readFullyWithFailingDataSource() throws Exception {
FakeDataSource testDataSource = buildFailingDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
try {
@@ -202,7 +202,7 @@
}
@Test
- public void testReadFullyHalfPeeked() throws Exception {
+ public void readFullyHalfPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -216,7 +216,7 @@
}
@Test
- public void testSkipMultipleTimes() throws Exception {
+ public void skipMultipleTimes() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
// We expect to perform three skips of three bytes, as setup in buildTestDataSource.
for (int i = 0; i < 3; i++) {
@@ -226,7 +226,7 @@
}
@Test
- public void testLargeSkip() throws Exception {
+ public void largeSkip() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
// Check that skipping the entire data source succeeds.
int bytesToSkip = LARGE_TEST_DATA_LENGTH;
@@ -236,7 +236,7 @@
}
@Test
- public void testSkipAlreadyPeeked() throws Exception {
+ public void skipAlreadyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
input.advancePeekPosition(TEST_DATA.length);
@@ -247,7 +247,7 @@
}
@Test
- public void testSkipPartiallyPeeked() throws Exception {
+ public void skipPartiallyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
input.advancePeekPosition(TEST_DATA.length - 1);
@@ -258,7 +258,7 @@
}
@Test
- public void testSkipEndOfInputBeforeFirstByteSkipped() throws Exception {
+ public void skipEndOfInputBeforeFirstByteSkipped() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(TEST_DATA.length);
@@ -269,7 +269,7 @@
}
@Test
- public void testSkipEndOfInputAfterFirstByteSkipped() throws Exception {
+ public void skipEndOfInputAfterFirstByteSkipped() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(TEST_DATA.length - 1);
@@ -280,7 +280,7 @@
}
@Test
- public void testSkipZeroLength() throws Exception {
+ public void skipZeroLength() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
int bytesRead = input.skip(0);
@@ -289,7 +289,7 @@
}
@Test
- public void testSkipFullyOnce() throws Exception {
+ public void skipFullyOnce() throws Exception {
// Skip TEST_DATA.
DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(TEST_DATA.length);
@@ -307,7 +307,7 @@
}
@Test
- public void testSkipFullyTwice() throws Exception {
+ public void skipFullyTwice() throws Exception {
// Skip TEST_DATA in two parts.
DefaultExtractorInput input = createDefaultExtractorInput();
input.skipFully(5);
@@ -317,7 +317,7 @@
}
@Test
- public void testSkipFullyTwicePeeked() throws Exception {
+ public void skipFullyTwicePeeked() throws Exception {
// Skip TEST_DATA.
DefaultExtractorInput input = createDefaultExtractorInput();
@@ -332,7 +332,7 @@
}
@Test
- public void testSkipFullyTooMuch() throws Exception {
+ public void skipFullyTooMuch() throws Exception {
// Skip more than TEST_DATA. Should fail with an EOFException. Position should not update.
DefaultExtractorInput input = createDefaultExtractorInput();
try {
@@ -356,7 +356,7 @@
}
@Test
- public void testSkipFullyWithFailingDataSource() throws Exception {
+ public void skipFullyWithFailingDataSource() throws Exception {
FakeDataSource testDataSource = buildFailingDataSource();
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
try {
@@ -370,7 +370,7 @@
}
@Test
- public void testSkipFullyLarge() throws Exception {
+ public void skipFullyLarge() throws Exception {
// Tests skipping an amount of data that's larger than any internal scratch space.
int largeSkipSize = 1024 * 1024;
FakeDataSource testDataSource = new FakeDataSource();
@@ -390,7 +390,7 @@
}
@Test
- public void testPeekMultipleTimes() throws Exception {
+ public void peekMultipleTimes() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -407,7 +407,7 @@
}
@Test
- public void testPeekAlreadyPeeked() throws Exception {
+ public void peekAlreadyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -422,7 +422,7 @@
}
@Test
- public void testPeekPartiallyPeeked() throws Exception {
+ public void peekPartiallyPeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -436,7 +436,7 @@
}
@Test
- public void testPeekEndOfInputBeforeFirstBytePeeked() throws Exception {
+ public void peekEndOfInputBeforeFirstBytePeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -448,7 +448,7 @@
}
@Test
- public void testPeekEndOfInputAfterFirstBytePeeked() throws Exception {
+ public void peekEndOfInputAfterFirstBytePeeked() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -460,7 +460,7 @@
}
@Test
- public void testPeekZeroLength() throws Exception {
+ public void peekZeroLength() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -470,7 +470,7 @@
}
@Test
- public void testPeekFully() throws Exception {
+ public void peekFully() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
input.peekFully(target, 0, TEST_DATA.length);
@@ -497,7 +497,7 @@
}
@Test
- public void testPeekFullyAfterEofExceptionPeeksAsExpected() throws Exception {
+ public void peekFullyAfterEofExceptionPeeksAsExpected() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length + 10];
@@ -514,7 +514,7 @@
}
@Test
- public void testResetPeekPosition() throws Exception {
+ public void resetPeekPosition() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
input.peekFully(target, 0, TEST_DATA.length);
@@ -539,7 +539,7 @@
}
@Test
- public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception {
+ public void peekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -551,7 +551,7 @@
}
@Test
- public void testPeekFullyAtEndThenReadEndOfInput() throws Exception {
+ public void peekFullyAtEndThenReadEndOfInput() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -569,7 +569,7 @@
}
@Test
- public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() throws Exception {
+ public void peekFullyAcrossEndOfInputWithAllowEndOfInputFails() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -586,7 +586,7 @@
}
@Test
- public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception {
+ public void resetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() throws Exception {
DefaultExtractorInput input = createDefaultExtractorInput();
byte[] target = new byte[TEST_DATA.length];
@@ -622,14 +622,6 @@
return testDataSource;
}
- private static FakeDataSource buildLargeDataSource() throws Exception {
- FakeDataSource testDataSource = new FakeDataSource();
- testDataSource.getDataSet().newDefaultData()
- .appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
- testDataSource.open(new DataSpec(Uri.parse(TEST_URI)));
- return testDataSource;
- }
-
private static DefaultExtractorInput createDefaultExtractorInput() throws Exception {
FakeDataSource testDataSource = buildDataSource();
return new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java
index ace30db..b24c76d 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java
@@ -42,7 +42,7 @@
public final class DefaultExtractorsFactoryTest {
@Test
- public void testCreateExtractors_returnExpectedClasses() {
+ public void createExtractors_returnExpectedClasses() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors();
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java
index 0401b2b..c95804c 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java
@@ -27,7 +27,7 @@
public final class ExtractorTest {
@Test
- public void testConstants() {
+ public void constants() {
// Sanity check that constant values match those defined by {@link C}.
assertThat(Extractor.RESULT_END_OF_INPUT).isEqualTo(C.RESULT_END_OF_INPUT);
// Sanity check that the other constant values don't overlap.
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java
index c8e8f86..9604b48 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ExtractorUtilTest.java
@@ -34,7 +34,7 @@
private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8};
@Test
- public void testPeekToLengthEndNotReached() throws Exception {
+ public void peekToLengthEndNotReached() throws Exception {
FakeDataSource testDataSource = new FakeDataSource();
testDataSource
.getDataSet()
@@ -57,7 +57,7 @@
}
@Test
- public void testPeekToLengthEndReached() throws Exception {
+ public void peekToLengthEndReached() throws Exception {
FakeDataSource testDataSource = new FakeDataSource();
testDataSource
.getDataSet()
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java
index 87487a4..9150493 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java
@@ -307,8 +307,7 @@
}
private ExtractorInput buildExtractorInputReadingFromFirstFrame(
- String file, FlacStreamMetadataHolder streamMetadataHolder)
- throws IOException, InterruptedException {
+ String file, FlacStreamMetadataHolder streamMetadataHolder) throws IOException {
ExtractorInput input = buildExtractorInput(file);
input.skipFully(FlacConstants.STREAM_MARKER_SIZE);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java
index fb53cb2..a6a2cd3 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java
@@ -45,7 +45,7 @@
@Test
public void peekId3Metadata_updatesPeekPosition() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false);
@@ -55,7 +55,7 @@
@Test
public void peekId3Metadata_parseData_returnsNonEmptyMetadata() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
Metadata metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ true);
@@ -65,7 +65,7 @@
@Test
public void peekId3Metadata_doNotParseData_returnsNull() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
Metadata metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false);
@@ -103,7 +103,7 @@
@Test
public void checkAndPeekStreamMarker_invalidData_isFalse() throws Exception {
- ExtractorInput input = buildExtractorInput("mp3/bear.mp3");
+ ExtractorInput input = buildExtractorInput("mp3/bear-vbr-xing-header.mp3");
boolean result = FlacMetadataReader.checkAndPeekStreamMarker(input);
@@ -112,7 +112,7 @@
@Test
public void readId3Metadata_updatesReadPositionAndAlignsPeekPosition() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
// Advance peek position after ID3 metadata.
FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false);
input.advancePeekPosition(1);
@@ -125,7 +125,7 @@
@Test
public void readId3Metadata_parseData_returnsNonEmptyMetadata() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
Metadata metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ true);
@@ -135,7 +135,7 @@
@Test
public void readId3Metadata_doNotParseData_returnsNull() throws Exception {
- ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac");
+ ExtractorInput input = buildExtractorInput("flac/bear_with_id3.flac");
Metadata metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ false);
@@ -163,7 +163,7 @@
@Test
public void readStreamMarker_invalidData_throwsException() throws Exception {
- ExtractorInput input = buildExtractorInput("mp3/bear.mp3");
+ ExtractorInput input = buildExtractorInput("mp3/bear-vbr-xing-header.mp3");
assertThrows(ParserException.class, () -> FlacMetadataReader.readStreamMarker(input));
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java
index 7868b78..2c7d7ad 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/Id3PeekerTest.java
@@ -35,8 +35,7 @@
public final class Id3PeekerTest {
@Test
- public void testPeekId3Data_returnNull_ifId3TagNotPresentAtBeginningOfInput()
- throws IOException, InterruptedException {
+ public void peekId3Data_returnNull_ifId3TagNotPresentAtBeginningOfInput() throws IOException {
Id3Peeker id3Peeker = new Id3Peeker();
FakeExtractorInput input =
new FakeExtractorInput.Builder()
@@ -48,8 +47,7 @@
}
@Test
- public void testPeekId3Data_returnId3Tag_ifId3TagPresent()
- throws IOException, InterruptedException {
+ public void peekId3Data_returnId3Tag_ifId3TagPresent() throws IOException {
Id3Peeker id3Peeker = new Id3Peeker();
FakeExtractorInput input =
new FakeExtractorInput.Builder()
@@ -69,8 +67,8 @@
}
@Test
- public void testPeekId3Data_returnId3TagAccordingToGivenPredicate_ifId3TagPresent()
- throws IOException, InterruptedException {
+ public void peekId3Data_returnId3TagAccordingToGivenPredicate_ifId3TagPresent()
+ throws IOException {
Id3Peeker id3Peeker = new Id3Peeker();
FakeExtractorInput input =
new FakeExtractorInput.Builder()
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisBitArrayTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisBitArrayTest.java
index b05cdd8..6d29af5 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisBitArrayTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisBitArrayTest.java
@@ -27,7 +27,7 @@
public final class VorbisBitArrayTest {
@Test
- public void testReadBit() {
+ public void readBit() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
assertThat(bitArray.readBit()).isFalse();
assertThat(bitArray.readBit()).isFalse();
@@ -48,7 +48,7 @@
}
@Test
- public void testSkipBits() {
+ public void skipBits() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
bitArray.skipBits(10);
assertThat(bitArray.getPosition()).isEqualTo(10);
@@ -62,7 +62,7 @@
}
@Test
- public void testGetPosition() throws Exception {
+ public void getPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
assertThat(bitArray.getPosition()).isEqualTo(0);
bitArray.readBit();
@@ -74,7 +74,7 @@
}
@Test
- public void testSetPosition() throws Exception {
+ public void setPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
assertThat(bitArray.getPosition()).isEqualTo(0);
bitArray.setPosition(4);
@@ -84,7 +84,7 @@
}
@Test
- public void testReadInt32() {
+ public void readInt32() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F, 0xF0, 0x0F));
assertThat(bitArray.readBits(32)).isEqualTo(0x0FF00FF0);
bitArray = new VorbisBitArray(TestUtil.createByteArray(0x0F, 0xF0, 0x0F, 0xF0));
@@ -92,7 +92,7 @@
}
@Test
- public void testReadBits() throws Exception {
+ public void readBits() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
assertThat(bitArray.readBits(2)).isEqualTo(3);
bitArray.skipBits(6);
@@ -104,7 +104,7 @@
}
@Test
- public void testRead4BitsBeyondBoundary() throws Exception {
+ public void read4BitsBeyondBoundary() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x2e, 0x10));
assertThat(bitArray.readBits(7)).isEqualTo(0x2e);
assertThat(bitArray.getPosition()).isEqualTo(7);
@@ -112,7 +112,7 @@
}
@Test
- public void testReadBitsBeyondByteBoundaries() throws Exception {
+ public void readBitsBeyondByteBoundaries() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
assertThat(bitArray.readBits(32)).isEqualTo(0x0FFF0FFF);
@@ -137,7 +137,7 @@
}
@Test
- public void testReadBitsIllegalLengths() throws Exception {
+ public void readBitsIllegalLengths() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22, 0x30));
// reading zero bits gets 0 without advancing position
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisUtilTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisUtilTest.java
index 15add33..67ac6bd 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisUtilTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/VorbisUtilTest.java
@@ -34,7 +34,7 @@
public final class VorbisUtilTest {
@Test
- public void testILog() throws Exception {
+ public void iLog_returnsHighestSetBit() {
assertThat(iLog(0)).isEqualTo(0);
assertThat(iLog(1)).isEqualTo(1);
assertThat(iLog(2)).isEqualTo(2);
@@ -47,7 +47,7 @@
}
@Test
- public void testReadIdHeader() throws Exception {
+ public void readIdHeader() throws Exception {
byte[] data =
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "binary/vorbis/id_header");
@@ -61,14 +61,13 @@
assertThat(vorbisIdHeader.channels).isEqualTo(2);
assertThat(vorbisIdHeader.blockSize0).isEqualTo(512);
assertThat(vorbisIdHeader.blockSize1).isEqualTo(1024);
- assertThat(vorbisIdHeader.bitrateMax).isEqualTo(-1);
- assertThat(vorbisIdHeader.bitrateMin).isEqualTo(-1);
+ assertThat(vorbisIdHeader.bitrateMaximum).isEqualTo(-1);
+ assertThat(vorbisIdHeader.bitrateMinimum).isEqualTo(-1);
assertThat(vorbisIdHeader.bitrateNominal).isEqualTo(66666);
- assertThat(vorbisIdHeader.getApproximateBitrate()).isEqualTo(66666);
}
@Test
- public void testReadCommentHeader() throws IOException {
+ public void readCommentHeader() throws IOException {
byte[] data =
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "binary/vorbis/comment_header");
@@ -83,7 +82,7 @@
}
@Test
- public void testReadVorbisModes() throws IOException {
+ public void readVorbisModes() throws IOException {
byte[] data =
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "binary/vorbis/setup_header");
@@ -102,14 +101,15 @@
}
@Test
- public void testVerifyVorbisHeaderCapturePattern() throws ParserException {
+ public void verifyVorbisHeaderCapturePattern_withValidHeader_returnsTrue()
+ throws ParserException {
ParsableByteArray header = new ParsableByteArray(
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
assertThat(verifyVorbisHeaderCapturePattern(0x01, header, false)).isTrue();
}
@Test
- public void testVerifyVorbisHeaderCapturePatternInvalidHeader() {
+ public void verifyVorbisHeaderCapturePattern_withValidHeader_returnsFalse() {
ParsableByteArray header = new ParsableByteArray(
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
try {
@@ -121,14 +121,15 @@
}
@Test
- public void testVerifyVorbisHeaderCapturePatternInvalidHeaderQuite() throws ParserException {
+ public void verifyVorbisHeaderCapturePattern_withInvalidHeaderQuite_returnsFalse()
+ throws ParserException {
ParsableByteArray header = new ParsableByteArray(
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
assertThat(verifyVorbisHeaderCapturePattern(0x99, header, true)).isFalse();
}
@Test
- public void testVerifyVorbisHeaderCapturePatternInvalidPattern() {
+ public void verifyVorbisHeaderCapturePattern_withInvalidPattern_returnsFalse() {
ParsableByteArray header = new ParsableByteArray(
new byte[] {0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
try {
@@ -140,7 +141,7 @@
}
@Test
- public void testVerifyVorbisHeaderCapturePatternQuiteInvalidPatternQuite()
+ public void verifyVorbisHeaderCapturePatternQuite_withInvalidPatternQuite_returnsFalse()
throws ParserException {
ParsableByteArray header = new ParsableByteArray(
new byte[] {0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorSeekTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorSeekTest.java
index d131fce..850321e 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorSeekTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorSeekTest.java
@@ -56,8 +56,7 @@
}
@Test
- public void testAmrExtractorReads_returnSeekableSeekMap_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void amrExtractorReads_returnSeekableSeekMap_forNarrowBandAmr() throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -76,8 +75,8 @@
}
@Test
- public void testSeeking_handlesSeekingToPositionInFile_extractsCorrectFrame_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingToPositionInFile_extractsCorrectFrame_forNarrowBandAmr()
+ throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -103,8 +102,7 @@
}
@Test
- public void testSeeking_handlesSeekToEoF_extractsLastFrame_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekToEoF_extractsLastFrame_forNarrowBandAmr() throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -130,8 +128,8 @@
}
@Test
- public void testSeeking_handlesSeekingBackward_extractsCorrectFrames_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingBackward_extractsCorrectFrames_forNarrowBandAmr()
+ throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -159,8 +157,8 @@
}
@Test
- public void testSeeking_handlesSeekingForward_extractsCorrectFrames_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingForward_extractsCorrectFrames_forNarrowBandAmr()
+ throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -188,8 +186,8 @@
}
@Test
- public void testSeeking_handlesRandomSeeks_extractsCorrectFrames_forNarrowBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesRandomSeeks_extractsCorrectFrames_forNarrowBandAmr()
+ throws IOException {
String fileName = NARROW_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -217,8 +215,7 @@
}
@Test
- public void testAmrExtractorReads_returnSeekableSeekMap_forWideBandAmr()
- throws IOException, InterruptedException {
+ public void amrExtractorReads_returnSeekableSeekMap_forWideBandAmr() throws IOException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -237,8 +234,8 @@
}
@Test
- public void testSeeking_handlesSeekingToPositionInFile_extractsCorrectFrame_forWideBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingToPositionInFile_extractsCorrectFrame_forWideBandAmr()
+ throws IOException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -264,8 +261,7 @@
}
@Test
- public void testSeeking_handlesSeekToEoF_extractsLastFrame_forWideBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekToEoF_extractsLastFrame_forWideBandAmr() throws IOException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -291,8 +287,8 @@
}
@Test
- public void testSeeking_handlesSeekingBackward_extractsCorrectFrames_forWideBandAmr()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingBackward_extractsCorrectFrames_forWideBandAmr()
+ throws IOException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -320,7 +316,7 @@
}
@Test
- public void testSeeking_handlesSeekingForward_extractsCorrectFrames_forWideBandAmr()
+ public void seeking_handlesSeekingForward_extractsCorrectFrames_forWideBandAmr()
throws IOException, InterruptedException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
@@ -349,7 +345,7 @@
}
@Test
- public void testSeeking_handlesRandomSeeks_extractsCorrectFrames_forWideBandAmr()
+ public void seeking_handlesRandomSeeks_extractsCorrectFrames_forWideBandAmr()
throws IOException, InterruptedException {
String fileName = WIDE_BAND_AMR_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java
index 7d6917d..03b6fcb 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java
@@ -42,7 +42,7 @@
private static final Random RANDOM = new Random(1234);
@Test
- public void testSniff_nonAmrSignature_returnFalse() throws IOException, InterruptedException {
+ public void sniff_nonAmrSignature_returnFalse() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
FakeExtractorInput input = fakeExtractorInputWithData(Util.getUtf8Bytes("0#!AMR\n123"));
@@ -51,8 +51,7 @@
}
@Test
- public void testRead_nonAmrSignature_throwParserException()
- throws IOException, InterruptedException {
+ public void read_nonAmrSignature_throwParserException() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
FakeExtractorInput input = fakeExtractorInputWithData(Util.getUtf8Bytes("0#!AMR-WB\n"));
@@ -65,8 +64,7 @@
}
@Test
- public void testRead_amrNb_returnParserException_forInvalidFrameType()
- throws IOException, InterruptedException {
+ public void read_amrNb_returnParserException_forInvalidFrameType() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
// Frame type 12-14 for narrow band is reserved for future usage.
@@ -83,8 +81,7 @@
}
@Test
- public void testRead_amrWb_returnParserException_forInvalidFrameType()
- throws IOException, InterruptedException {
+ public void read_amrWb_returnParserException_forInvalidFrameType() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
// Frame type 10-13 for wide band is reserved for future usage.
@@ -101,8 +98,7 @@
}
@Test
- public void testRead_amrNb_returnEndOfInput_ifInputEncountersEoF()
- throws IOException, InterruptedException {
+ public void read_amrNb_returnEndOfInput_ifInputEncountersEoF() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
byte[] amrFrame = newNarrowBandAmrFrameWithType(3);
@@ -117,8 +113,7 @@
}
@Test
- public void testRead_amrWb_returnEndOfInput_ifInputEncountersEoF()
- throws IOException, InterruptedException {
+ public void read_amrWb_returnEndOfInput_ifInputEncountersEoF() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
byte[] amrFrame = newWideBandAmrFrameWithType(5);
@@ -133,8 +128,7 @@
}
@Test
- public void testRead_amrNb_returnParserException_forInvalidFrameHeader()
- throws IOException, InterruptedException {
+ public void read_amrNb_returnParserException_forInvalidFrameHeader() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
byte[] invalidHeaderFrame = newNarrowBandAmrFrameWithType(4);
@@ -155,8 +149,7 @@
}
@Test
- public void testRead_amrWb_returnParserException_forInvalidFrameHeader()
- throws IOException, InterruptedException {
+ public void read_amrWb_returnParserException_forInvalidFrameHeader() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
byte[] invalidHeaderFrame = newWideBandAmrFrameWithType(6);
@@ -177,25 +170,25 @@
}
@Test
- public void testExtractingNarrowBandSamples() throws Exception {
+ public void extractingNarrowBandSamples() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ false), "amr/sample_nb.amr");
}
@Test
- public void testExtractingWideBandSamples() throws Exception {
+ public void extractingWideBandSamples() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ false), "amr/sample_wb.amr");
}
@Test
- public void testExtractingNarrowBandSamples_withSeeking() throws Exception {
+ public void extractingNarrowBandSamples_withSeeking() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ true), "amr/sample_nb_cbr.amr");
}
@Test
- public void testExtractingWideBandSamples_withSeeking() throws Exception {
+ public void extractingWideBandSamples_withSeeking() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ true), "amr/sample_wb_cbr.amr");
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorSeekTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorSeekTest.java
index 5b53c2f..99cf464 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorSeekTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorSeekTest.java
@@ -30,7 +30,6 @@
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.List;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,22 +42,14 @@
private static final String TEST_FILE_UNSEEKABLE = "flac/bear_no_seek_table_no_num_samples.flac";
private static final int DURATION_US = 2_741_000;
- private FlacExtractor extractor;
- private FakeExtractorOutput extractorOutput;
- private DefaultDataSource dataSource;
-
- @Before
- public void setUp() throws Exception {
- extractor = new FlacExtractor();
- extractorOutput = new FakeExtractorOutput();
- dataSource =
- new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
- .createDataSource();
- }
+ private FlacExtractor extractor = new FlacExtractor();
+ private FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
+ private DefaultDataSource dataSource =
+ new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
+ .createDataSource();
@Test
- public void flacExtractorReads_seekTable_returnSeekableSeekMap()
- throws IOException, InterruptedException {
+ public void flacExtractorReads_seekTable_returnSeekableSeekMap() throws IOException {
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_SEEK_TABLE);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -69,7 +60,7 @@
}
@Test
- public void seeking_seekTable_handlesSeekToZero() throws IOException, InterruptedException {
+ public void seeking_seekTable_handlesSeekToZero() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -86,7 +77,7 @@
}
@Test
- public void seeking_seekTable_handlesSeekToEoF() throws IOException, InterruptedException {
+ public void seeking_seekTable_handlesSeekToEoF() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -103,7 +94,7 @@
}
@Test
- public void seeking_seekTable_handlesSeekingBackward() throws IOException, InterruptedException {
+ public void seeking_seekTable_handlesSeekingBackward() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -122,7 +113,7 @@
}
@Test
- public void seeking_seekTable_handlesSeekingForward() throws IOException, InterruptedException {
+ public void seeking_seekTable_handlesSeekingForward() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -141,8 +132,7 @@
}
@Test
- public void flacExtractorReads_binarySearch_returnSeekableSeekMap()
- throws IOException, InterruptedException {
+ public void flacExtractorReads_binarySearch_returnSeekableSeekMap() throws IOException {
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_BINARY_SEARCH);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -153,7 +143,7 @@
}
@Test
- public void seeking_binarySearch_handlesSeekToZero() throws IOException, InterruptedException {
+ public void seeking_binarySearch_handlesSeekToZero() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -170,7 +160,7 @@
}
@Test
- public void seeking_binarySearch_handlesSeekToEoF() throws IOException, InterruptedException {
+ public void seeking_binarySearch_handlesSeekToEoF() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -187,8 +177,7 @@
}
@Test
- public void seeking_binarySearch_handlesSeekingBackward()
- throws IOException, InterruptedException {
+ public void seeking_binarySearch_handlesSeekingBackward() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -207,8 +196,7 @@
}
@Test
- public void seeking_binarySearch_handlesSeekingForward()
- throws IOException, InterruptedException {
+ public void seeking_binarySearch_handlesSeekingForward() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
Uri fileUri = TestUtil.buildAssetUri(fileName);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -227,8 +215,7 @@
}
@Test
- public void flacExtractorReads_unseekable_returnUnseekableSeekMap()
- throws IOException, InterruptedException {
+ public void flacExtractorReads_unseekable_returnUnseekableSeekMap() throws IOException {
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_UNSEEKABLE);
SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
@@ -243,7 +230,7 @@
FakeTrackOutput trackOutput,
long targetSeekTimeUs,
int firstFrameIndexAfterSeek)
- throws IOException, InterruptedException {
+ throws IOException {
FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
int expectedFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
@@ -260,7 +247,7 @@
FakeTrackOutput trackOutput,
long targetSeekTimeUs,
int firstFrameIndexAfterSeek)
- throws IOException, InterruptedException {
+ throws IOException {
FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
int maxFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
@@ -284,8 +271,7 @@
assertThat(frameFound).isTrue();
}
- private static FakeTrackOutput getExpectedTrackOutput(String fileName)
- throws IOException, InterruptedException {
+ private static FakeTrackOutput getExpectedTrackOutput(String fileName) throws IOException {
return TestUtil.extractAllSamplesFromFile(
new FlacExtractor(), ApplicationProvider.getApplicationContext(), fileName)
.trackOutputs
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorTest.java
index 061d090..fab950a 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flac/FlacExtractorTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.flac;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import org.junit.Test;
@@ -25,57 +26,92 @@
public class FlacExtractorTest {
@Test
- public void testSample() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear.flac");
+ public void sample() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_flac");
}
@Test
- public void testSampleWithId3HeaderAndId3Enabled() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_id3_enabled.flac");
+ public void sampleWithId3HeaderAndId3Enabled() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_id3.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_id3_enabled_flac");
}
@Test
- public void testSampleWithId3HeaderAndId3Disabled() throws Exception {
- // bear_with_id3_disabled.flac is identical to bear_with_id3_enabled.flac, but the dump file is
- // different due to setting FLAG_DISABLE_ID3_METADATA.
+ public void sampleWithId3HeaderAndId3Disabled() throws Exception {
ExtractorAsserts.assertBehavior(
() -> new FlacExtractor(FlacExtractor.FLAG_DISABLE_ID3_METADATA),
- "flac/bear_with_id3_disabled.flac");
+ /* file= */ "flac/bear_with_id3.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_id3_disabled_flac");
}
@Test
- public void testSampleUnseekable() throws Exception {
+ public void sampleUnseekable() throws Exception {
ExtractorAsserts.assertBehavior(
- FlacExtractor::new, "flac/bear_no_seek_table_no_num_samples.flac");
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_seek_table_no_num_samples.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_seek_table_no_num_samples_flac");
}
@Test
- public void testSampleWithVorbisComments() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_vorbis_comments.flac");
+ public void sampleWithVorbisComments() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_vorbis_comments.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_vorbis_comments_flac");
}
@Test
- public void testSampleWithPicture() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_picture.flac");
+ public void sampleWithPicture() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_with_picture.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_with_picture_flac");
}
@Test
- public void testOneMetadataBlock() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_one_metadata_block.flac");
+ public void oneMetadataBlock() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_one_metadata_block.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_one_metadata_block_flac");
}
@Test
- public void testNoMinMaxFrameSize() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_no_min_max_frame_size.flac");
+ public void noMinMaxFrameSize() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_min_max_frame_size.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_min_max_frame_size_flac");
}
@Test
- public void testNoNumSamples() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_no_num_samples.flac");
+ public void noNumSamples() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_no_num_samples.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_no_num_samples_flac");
}
@Test
- public void testUncommonSampleRate() throws Exception {
- ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac");
+ public void uncommonSampleRate() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ FlacExtractor::new,
+ /* file= */ "flac/bear_uncommon_sample_rate.flac",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "flac/bear_uncommon_sample_rate_flac");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java
index e050509..52b6a04 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java
@@ -25,7 +25,7 @@
public final class FlvExtractorTest {
@Test
- public void testSample() throws Exception {
+ public void sample() throws Exception {
ExtractorAsserts.assertBehavior(FlvExtractor::new, "flv/sample.flv");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java
index 642b994..a8275fd 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java
@@ -33,7 +33,7 @@
public class DefaultEbmlReaderTest {
@Test
- public void testMasterElement() throws IOException, InterruptedException {
+ public void masterElement() throws IOException {
ExtractorInput input = createTestInput(0x1A, 0x45, 0xDF, 0xA3, 0x84, 0x42, 0x85, 0x81, 0x01);
TestProcessor expected = new TestProcessor();
expected.startMasterElement(TestProcessor.ID_EBML, 5, 4);
@@ -43,7 +43,7 @@
}
@Test
- public void testMasterElementEmpty() throws IOException, InterruptedException {
+ public void masterElementEmpty() throws IOException {
ExtractorInput input = createTestInput(0x18, 0x53, 0x80, 0x67, 0x80);
TestProcessor expected = new TestProcessor();
expected.startMasterElement(TestProcessor.ID_SEGMENT, 5, 0);
@@ -52,7 +52,7 @@
}
@Test
- public void testUnsignedIntegerElement() throws IOException, InterruptedException {
+ public void unsignedIntegerElement() throws IOException {
// 0xFE is chosen because for signed integers it should be interpreted as -2
ExtractorInput input = createTestInput(0x42, 0xF7, 0x81, 0xFE);
TestProcessor expected = new TestProcessor();
@@ -61,7 +61,7 @@
}
@Test
- public void testUnsignedIntegerElementLarge() throws IOException, InterruptedException {
+ public void unsignedIntegerElementLarge() throws IOException {
ExtractorInput input =
createTestInput(0x42, 0xF7, 0x88, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
TestProcessor expected = new TestProcessor();
@@ -70,8 +70,7 @@
}
@Test
- public void testUnsignedIntegerElementTooLargeBecomesNegative()
- throws IOException, InterruptedException {
+ public void unsignedIntegerElementTooLargeBecomesNegative() throws IOException {
ExtractorInput input =
createTestInput(0x42, 0xF7, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
TestProcessor expected = new TestProcessor();
@@ -80,7 +79,7 @@
}
@Test
- public void testStringElement() throws IOException, InterruptedException {
+ public void stringElement() throws IOException {
ExtractorInput input = createTestInput(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x31, 0x32, 0x33);
TestProcessor expected = new TestProcessor();
expected.stringElement(TestProcessor.ID_DOC_TYPE, "Abc123");
@@ -88,7 +87,7 @@
}
@Test
- public void testStringElementWithZeroPadding() throws IOException, InterruptedException {
+ public void stringElementWithZeroPadding() throws IOException, InterruptedException {
ExtractorInput input = createTestInput(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x00, 0x00, 0x00);
TestProcessor expected = new TestProcessor();
expected.stringElement(TestProcessor.ID_DOC_TYPE, "Abc");
@@ -96,7 +95,7 @@
}
@Test
- public void testStringElementEmpty() throws IOException, InterruptedException {
+ public void stringElementEmpty() throws IOException {
ExtractorInput input = createTestInput(0x42, 0x82, 0x80);
TestProcessor expected = new TestProcessor();
expected.stringElement(TestProcessor.ID_DOC_TYPE, "");
@@ -104,7 +103,7 @@
}
@Test
- public void testFloatElementFourBytes() throws IOException, InterruptedException {
+ public void floatElementFourBytes() throws IOException {
ExtractorInput input =
createTestInput(0x44, 0x89, 0x84, 0x3F, 0x80, 0x00, 0x00);
TestProcessor expected = new TestProcessor();
@@ -113,7 +112,7 @@
}
@Test
- public void testFloatElementEightBytes() throws IOException, InterruptedException {
+ public void floatElementEightBytes() throws IOException {
ExtractorInput input =
createTestInput(0x44, 0x89, 0x88, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
TestProcessor expected = new TestProcessor();
@@ -122,7 +121,7 @@
}
@Test
- public void testBinaryElement() throws IOException, InterruptedException {
+ public void binaryElement() throws IOException {
ExtractorInput input =
createTestInput(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
TestProcessor expected = new TestProcessor();
@@ -134,7 +133,7 @@
}
private static void assertEvents(ExtractorInput input, List<String> expectedEvents)
- throws IOException, InterruptedException {
+ throws IOException {
DefaultEbmlReader reader = new DefaultEbmlReader();
TestProcessor output = new TestProcessor();
reader.init(output);
@@ -232,8 +231,7 @@
}
@Override
- public void binaryElement(int id, int contentSize, ExtractorInput input)
- throws IOException, InterruptedException {
+ public void binaryElement(int id, int contentSize, ExtractorInput input) throws IOException {
byte[] bytes = new byte[contentSize];
input.readFully(bytes, 0, contentSize);
events.add(formatEvent(id, "bytes=" + Arrays.toString(bytes)));
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java
index 80eb33a..761815c 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java
@@ -25,23 +25,23 @@
public final class MatroskaExtractorTest {
@Test
- public void testMkvSample() throws Exception {
+ public void mkvSample() throws Exception {
ExtractorAsserts.assertBehavior(MatroskaExtractor::new, "mkv/sample.mkv");
}
@Test
- public void testMkvFullBlocksSample() throws Exception {
+ public void mkvFullBlocksSample() throws Exception {
ExtractorAsserts.assertBehavior(MatroskaExtractor::new, "mkv/full_blocks.mkv");
}
@Test
- public void testWebmSubsampleEncryption() throws Exception {
+ public void webmSubsampleEncryption() throws Exception {
ExtractorAsserts.assertBehavior(
MatroskaExtractor::new, "mkv/subsample_encrypted_noaltref.webm");
}
@Test
- public void testWebmSubsampleEncryptionWithAltrefFrames() throws Exception {
+ public void webmSubsampleEncryptionWithAltrefFrames() throws Exception {
ExtractorAsserts.assertBehavior(MatroskaExtractor::new, "mkv/subsample_encrypted_altref.webm");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java
index 86df3f5..7a3c73e 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java
@@ -85,7 +85,7 @@
private static final long VALUE_8_BYTE_MAX_WITH_MASK = 0x1FFFFFFFFFFFFFFL;
@Test
- public void testReadVarintEndOfInputAtStart() throws IOException, InterruptedException {
+ public void readVarintEndOfInputAtStart() throws IOException {
VarintReader reader = new VarintReader();
// Build an input with no data.
ExtractorInput input = new FakeExtractorInput.Builder()
@@ -104,7 +104,7 @@
}
@Test
- public void testReadVarintExceedsMaximumAllowedLength() throws IOException, InterruptedException {
+ public void readVarintExceedsMaximumAllowedLength() throws IOException {
VarintReader reader = new VarintReader();
ExtractorInput input = new FakeExtractorInput.Builder()
.setData(DATA_8_BYTE_0)
@@ -115,7 +115,7 @@
}
@Test
- public void testReadVarint() throws IOException, InterruptedException {
+ public void readVarint() throws IOException {
VarintReader reader = new VarintReader();
testReadVarint(reader, true, DATA_1_BYTE_0, 1, 0);
testReadVarint(reader, true, DATA_2_BYTE_0, 2, 0);
@@ -152,7 +152,7 @@
}
@Test
- public void testReadVarintFlaky() throws IOException, InterruptedException {
+ public void readVarintFlaky() throws IOException {
VarintReader reader = new VarintReader();
testReadVarintFlaky(reader, true, DATA_1_BYTE_0, 1, 0);
testReadVarintFlaky(reader, true, DATA_2_BYTE_0, 2, 0);
@@ -188,8 +188,9 @@
testReadVarintFlaky(reader, false, DATA_8_BYTE_MAX, 8, VALUE_8_BYTE_MAX_WITH_MASK);
}
- private static void testReadVarint(VarintReader reader, boolean removeMask, byte[] data,
- int expectedLength, long expectedValue) throws IOException, InterruptedException {
+ private static void testReadVarint(
+ VarintReader reader, boolean removeMask, byte[] data, int expectedLength, long expectedValue)
+ throws IOException {
ExtractorInput input = new FakeExtractorInput.Builder()
.setData(data)
.setSimulateUnknownLength(true)
@@ -199,8 +200,9 @@
assertThat(result).isEqualTo(expectedValue);
}
- private static void testReadVarintFlaky(VarintReader reader, boolean removeMask, byte[] data,
- int expectedLength, long expectedValue) throws IOException, InterruptedException {
+ private static void testReadVarintFlaky(
+ VarintReader reader, boolean removeMask, byte[] data, int expectedLength, long expectedValue)
+ throws IOException {
ExtractorInput input = new FakeExtractorInput.Builder()
.setData(data)
.setSimulateUnknownLength(true)
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeekerTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeekerTest.java
new file mode 100644
index 0000000..8ff5e84
--- /dev/null
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeekerTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.extractor.mp3;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
+import com.google.android.exoplayer2.testutil.FakeTrackOutput;
+import com.google.android.exoplayer2.testutil.TestUtil;
+import com.google.android.exoplayer2.upstream.DefaultDataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link ConstantBitrateSeeker}. */
+@RunWith(AndroidJUnit4.class)
+public class ConstantBitrateSeekerTest {
+ private static final String CONSTANT_FRAME_SIZE_TEST_FILE =
+ "mp3/bear-cbr-constant-frame-size-no-seek-table.mp3";
+ private static final String VARIABLE_FRAME_SIZE_TEST_FILE =
+ "mp3/bear-cbr-variable-frame-size-no-seek-table.mp3";
+
+ private Mp3Extractor extractor;
+ private FakeExtractorOutput extractorOutput;
+ private DefaultDataSource dataSource;
+
+ @Before
+ public void setUp() throws Exception {
+ extractor = new Mp3Extractor();
+ extractorOutput = new FakeExtractorOutput();
+ dataSource =
+ new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
+ .createDataSource();
+ }
+
+ @Test
+ public void mp3ExtractorReads_returnSeekableCbrSeeker() throws IOException {
+ Uri fileUri = TestUtil.buildAssetUri(CONSTANT_FRAME_SIZE_TEST_FILE);
+
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+
+ assertThat(seekMap.getClass()).isEqualTo(ConstantBitrateSeeker.class);
+ assertThat(seekMap.getDurationUs()).isEqualTo(2_784_000);
+ assertThat(seekMap.isSeekable()).isTrue();
+ }
+
+ @Test
+ public void seeking_handlesSeekToZero() throws IOException {
+ String fileName = CONSTANT_FRAME_SIZE_TEST_FILE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long targetSeekTimeUs = 0;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsExactFrame(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekToEoF() throws IOException {
+ String fileName = CONSTANT_FRAME_SIZE_TEST_FILE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long targetSeekTimeUs = seekMap.getDurationUs();
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsExactFrame(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekingBackward() throws IOException {
+ String fileName = CONSTANT_FRAME_SIZE_TEST_FILE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long firstSeekTimeUs = 1_234_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 987_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsExactFrame(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekingForward() throws IOException {
+ String fileName = CONSTANT_FRAME_SIZE_TEST_FILE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long firstSeekTimeUs = 987_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 1_234_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsExactFrame(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_variableFrameSize_seeksNearlyExactlyToCorrectFrame() throws IOException {
+ String fileName = VARIABLE_FRAME_SIZE_TEST_FILE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long targetSeekTimeUs = 1_234_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsWithin1FrameOfExactFrame(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ }
+
+ private static void assertFirstFrameAfterSeekIsExactFrame(
+ String fileName,
+ FakeTrackOutput trackOutput,
+ long targetSeekTimeUs,
+ int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ int exactFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
+
+ assertThat(trackOutput.getSampleData(firstFrameIndexAfterSeek))
+ .isEqualTo(expectedTrackOutput.getSampleData(exactFrameIndex));
+ }
+
+ private static void assertFirstFrameAfterSeekIsWithin1FrameOfExactFrame(
+ String fileName,
+ FakeTrackOutput trackOutput,
+ long targetSeekTimeUs,
+ int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ int exactFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
+
+ boolean foundPreviousFrame =
+ exactFrameIndex != 0
+ && Arrays.equals(
+ trackOutput.getSampleData(firstFrameIndexAfterSeek),
+ expectedTrackOutput.getSampleData(exactFrameIndex - 1));
+ boolean foundExactFrame =
+ Arrays.equals(
+ trackOutput.getSampleData(firstFrameIndexAfterSeek),
+ expectedTrackOutput.getSampleData(exactFrameIndex));
+ boolean foundNextFrame =
+ exactFrameIndex != expectedTrackOutput.getSampleCount() - 1
+ && Arrays.equals(
+ trackOutput.getSampleData(firstFrameIndexAfterSeek),
+ expectedTrackOutput.getSampleData(exactFrameIndex + 1));
+
+ assertThat(foundPreviousFrame || foundExactFrame || foundNextFrame).isTrue();
+ }
+
+ private static FakeTrackOutput getExpectedTrackOutput(String fileName) throws IOException {
+ return TestUtil.extractAllSamplesFromFile(
+ new Mp3Extractor(), ApplicationProvider.getApplicationContext(), fileName)
+ .trackOutputs
+ .get(0);
+ }
+
+ private static int getFrameIndex(FakeTrackOutput trackOutput, long targetSeekTimeUs) {
+ List<Long> frameTimes = trackOutput.getSampleTimesUs();
+ return Util.binarySearchFloor(
+ frameTimes, targetSeekTimeUs, /* inclusive= */ true, /* stayInBounds= */ false);
+ }
+}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/IndexSeekerTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/IndexSeekerTest.java
new file mode 100644
index 0000000..0e5c263
--- /dev/null
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/IndexSeekerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.extractor.mp3;
+
+import static com.google.android.exoplayer2.extractor.mp3.Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING;
+import static com.google.android.exoplayer2.testutil.TestUtil.extractAllSamplesFromFile;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
+import com.google.android.exoplayer2.testutil.FakeTrackOutput;
+import com.google.android.exoplayer2.testutil.TestUtil;
+import com.google.android.exoplayer2.upstream.DefaultDataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link IndexSeeker}. */
+@RunWith(AndroidJUnit4.class)
+public class IndexSeekerTest {
+
+ private static final String TEST_FILE_NO_SEEK_TABLE = "mp3/bear-vbr-no-seek-table.mp3";
+ private static final int TEST_FILE_NO_SEEK_TABLE_DURATION = 2_808_000;
+
+ private Mp3Extractor extractor;
+ private FakeExtractorOutput extractorOutput;
+ private DefaultDataSource dataSource;
+
+ @Before
+ public void setUp() throws Exception {
+ extractor = new Mp3Extractor(FLAG_ENABLE_INDEX_SEEKING);
+ extractorOutput = new FakeExtractorOutput();
+ dataSource =
+ new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
+ .createDataSource();
+ }
+
+ @Test
+ public void mp3ExtractorReads_returnsSeekableSeekMap() throws Exception {
+ Uri fileUri = TestUtil.buildAssetUri(TEST_FILE_NO_SEEK_TABLE);
+
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+
+ assertThat(seekMap.isSeekable()).isTrue();
+ }
+
+ @Test
+ public void mp3ExtractorReads_correctsInexactDuration() throws Exception {
+ FakeExtractorOutput extractorOutput =
+ TestUtil.extractAllSamplesFromFile(
+ extractor, ApplicationProvider.getApplicationContext(), TEST_FILE_NO_SEEK_TABLE);
+
+ SeekMap seekMap = extractorOutput.seekMap;
+
+ assertThat(seekMap.getDurationUs()).isEqualTo(TEST_FILE_NO_SEEK_TABLE_DURATION);
+ }
+
+ @Test
+ public void seeking_handlesSeekToZero() throws Exception {
+ String fileName = TEST_FILE_NO_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long targetSeekTimeUs = 0;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsWithinMinDifference(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertFirstFrameAfterSeekHasCorrectData(fileName, trackOutput, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekToEof() throws Exception {
+ String fileName = TEST_FILE_NO_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long targetSeekTimeUs = TEST_FILE_NO_SEEK_TABLE_DURATION;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsWithinMinDifference(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertFirstFrameAfterSeekHasCorrectData(fileName, trackOutput, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekingBackward() throws Exception {
+ String fileName = TEST_FILE_NO_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long firstSeekTimeUs = 1_234_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 987_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsWithinMinDifference(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertFirstFrameAfterSeekHasCorrectData(fileName, trackOutput, extractedFrameIndex);
+ }
+
+ @Test
+ public void seeking_handlesSeekingForward() throws Exception {
+ String fileName = TEST_FILE_NO_SEEK_TABLE;
+ Uri fileUri = TestUtil.buildAssetUri(fileName);
+ SeekMap seekMap = TestUtil.extractSeekMap(extractor, extractorOutput, dataSource, fileUri);
+ FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
+
+ long firstSeekTimeUs = 987_000;
+ TestUtil.seekToTimeUs(extractor, seekMap, firstSeekTimeUs, dataSource, trackOutput, fileUri);
+ long targetSeekTimeUs = 1_234_000;
+ int extractedFrameIndex =
+ TestUtil.seekToTimeUs(
+ extractor, seekMap, targetSeekTimeUs, dataSource, trackOutput, fileUri);
+
+ assertThat(extractedFrameIndex).isNotEqualTo(C.INDEX_UNSET);
+ assertFirstFrameAfterSeekIsWithinMinDifference(
+ fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
+ assertFirstFrameAfterSeekHasCorrectData(fileName, trackOutput, extractedFrameIndex);
+ }
+
+ private static void assertFirstFrameAfterSeekIsWithinMinDifference(
+ String fileName,
+ FakeTrackOutput trackOutput,
+ long targetSeekTimeUs,
+ int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ int exactFrameIndex = getFrameIndex(expectedTrackOutput, targetSeekTimeUs);
+ long exactFrameTimeUs = expectedTrackOutput.getSampleTimeUs(exactFrameIndex);
+ long foundTimeUs = trackOutput.getSampleTimeUs(firstFrameIndexAfterSeek);
+
+ assertThat(exactFrameTimeUs - foundTimeUs).isAtMost(IndexSeeker.MIN_TIME_BETWEEN_POINTS_US);
+ }
+
+ private static void assertFirstFrameAfterSeekHasCorrectData(
+ String fileName, FakeTrackOutput trackOutput, int firstFrameIndexAfterSeek)
+ throws IOException {
+ FakeTrackOutput expectedTrackOutput = getExpectedTrackOutput(fileName);
+ long foundTimeUs = trackOutput.getSampleTimeUs(firstFrameIndexAfterSeek);
+ int foundFrameIndex = getFrameIndex(expectedTrackOutput, foundTimeUs);
+
+ trackOutput.assertSample(
+ firstFrameIndexAfterSeek,
+ expectedTrackOutput.getSampleData(foundFrameIndex),
+ expectedTrackOutput.getSampleTimeUs(foundFrameIndex),
+ expectedTrackOutput.getSampleFlags(foundFrameIndex),
+ expectedTrackOutput.getSampleCryptoData(foundFrameIndex));
+ }
+
+ private static FakeTrackOutput getExpectedTrackOutput(String fileName) throws IOException {
+ return extractAllSamplesFromFile(
+ new Mp3Extractor(FLAG_ENABLE_INDEX_SEEKING),
+ ApplicationProvider.getApplicationContext(),
+ fileName)
+ .trackOutputs
+ .get(0);
+ }
+
+ private static int getFrameIndex(FakeTrackOutput trackOutput, long targetSeekTimeUs) {
+ List<Long> frameTimes = trackOutput.getSampleTimesUs();
+ return Util.binarySearchFloor(
+ frameTimes, targetSeekTimeUs, /* inclusive= */ true, /* stayInBounds= */ false);
+ }
+}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
index 670296c..554c032 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
@@ -25,12 +25,25 @@
public final class Mp3ExtractorTest {
@Test
- public void testMp3Sample() throws Exception {
- ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/bear.mp3");
+ public void mp3SampleWithXingHeader() throws Exception {
+ ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/bear-vbr-xing-header.mp3");
}
@Test
- public void testTrimmedMp3Sample() throws Exception {
+ public void mp3SampleWithCbrSeeker() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ Mp3Extractor::new, "mp3/bear-cbr-variable-frame-size-no-seek-table.mp3");
+ }
+
+ @Test
+ public void mp3SampleWithIndexSeeker() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ () -> new Mp3Extractor(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING),
+ "mp3/bear-vbr-no-seek-table.mp3");
+ }
+
+ @Test
+ public void trimmedMp3Sample() throws Exception {
ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/play-trimmed.mp3");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java
index 5ebd1b2..7c48183 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java
@@ -69,19 +69,19 @@
}
@Test
- public void testGetTimeUsBeforeFirstAudioFrame() {
+ public void getTimeUsBeforeFirstAudioFrame() {
assertThat(seeker.getTimeUs(-1)).isEqualTo(0);
assertThat(seekerWithInputLength.getTimeUs(-1)).isEqualTo(0);
}
@Test
- public void testGetTimeUsAtFirstAudioFrame() {
+ public void getTimeUsAtFirstAudioFrame() {
assertThat(seeker.getTimeUs(XING_FRAME_POSITION + xingFrameSize)).isEqualTo(0);
assertThat(seekerWithInputLength.getTimeUs(XING_FRAME_POSITION + xingFrameSize)).isEqualTo(0);
}
@Test
- public void testGetTimeUsAtEndOfStream() {
+ public void getTimeUsAtEndOfStream() {
assertThat(seeker.getTimeUs(STREAM_LENGTH))
.isEqualTo(STREAM_DURATION_US);
assertThat(
@@ -90,7 +90,7 @@
}
@Test
- public void testGetSeekPointsAtStartOfStream() {
+ public void getSeekPointsAtStartOfStream() {
SeekPoints seekPoints = seeker.getSeekPoints(0);
SeekPoint seekPoint = seekPoints.first;
assertThat(seekPoint).isEqualTo(seekPoints.second);
@@ -99,7 +99,7 @@
}
@Test
- public void testGetSeekPointsAtEndOfStream() {
+ public void getSeekPointsAtEndOfStream() {
SeekPoints seekPoints = seeker.getSeekPoints(STREAM_DURATION_US);
SeekPoint seekPoint = seekPoints.first;
assertThat(seekPoint).isEqualTo(seekPoints.second);
@@ -108,7 +108,7 @@
}
@Test
- public void testGetTimeForAllPositions() {
+ public void getTimeForAllPositions() {
for (int offset = xingFrameSize; offset < DATA_SIZE_BYTES; offset++) {
int position = XING_FRAME_POSITION + offset;
// Test seeker.
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java
index 712f8e4..9ca974a 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java
@@ -37,7 +37,7 @@
+ SAMPLE_COUNT + "0001000200030004");
@Test
- public void testParseCommonEncryptionSinfFromParentIgnoresUnknownSchemeType() {
+ public void parseCommonEncryptionSinfFromParentIgnoresUnknownSchemeType() {
byte[] cencSinf = new byte[] {
0, 0, 0, 24, 115, 105, 110, 102, // size (4), 'sinf' (4)
0, 0, 0, 16, 115, 99, 104, 109, // size (4), 'schm' (4)
@@ -47,17 +47,17 @@
}
@Test
- public void testStz2Parsing4BitFieldSize() {
+ public void stz2Parsing4BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2)));
}
@Test
- public void testStz2Parsing8BitFieldSize() {
+ public void stz2Parsing8BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2)));
}
@Test
- public void testStz2Parsing16BitFieldSize() {
+ public void stz2Parsing16BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2)));
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
index 28e0b9e..86f8e84 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
@@ -20,6 +20,7 @@
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -30,40 +31,63 @@
public final class FragmentedMp4ExtractorTest {
@Test
- public void testSample() throws Exception {
+ public void sample() throws Exception {
ExtractorAsserts.assertBehavior(
- getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented.mp4");
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_fragmented.mp4");
}
@Test
- public void testSampleSeekable() throws Exception {
+ public void sampleSeekable() throws Exception {
ExtractorAsserts.assertBehavior(
- getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented_seekable.mp4");
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_fragmented_seekable.mp4");
}
@Test
- public void testSampleWithSeiPayloadParsing() throws Exception {
+ public void sampleWithSeiPayloadParsing() throws Exception {
// Enabling the CEA-608 track enables SEI payload parsing.
ExtractorFactory extractorFactory =
getExtractorFactory(
Collections.singletonList(
- Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null)));
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_CEA608).build()));
ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4");
}
@Test
- public void testSampleWithAc4Track() throws Exception {
+ public void sampleWithAc3Track() throws Exception {
ExtractorAsserts.assertBehavior(
- getExtractorFactory(Collections.emptyList()), "mp4/sample_ac4_fragmented.mp4");
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_ac3_fragmented.mp4");
}
@Test
- public void testSampleWithProtectedAc4Track() throws Exception {
+ public void sampleWithAc4Track() throws Exception {
ExtractorAsserts.assertBehavior(
- getExtractorFactory(Collections.emptyList()), "mp4/sample_ac4_protected.mp4");
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_ac4_fragmented.mp4");
+ }
+
+ @Test
+ public void sampleWithProtectedAc4Track() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_ac4_protected.mp4");
+ }
+
+ @Test
+ public void sampleWithEac3Track() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_eac3_fragmented.mp4");
+ }
+
+ @Test
+ public void sampleWithEac3jocTrack() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ getExtractorFactory(ImmutableList.of()), "mp4/sample_eac3joc_fragmented.mp4");
}
private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) {
- return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats);
+ return () ->
+ new FragmentedMp4Extractor(
+ /* flags= */ 0,
+ /* timestampAdjuster= */ null,
+ /* sideloadedTrack= */ null,
+ closedCaptionFormats);
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java
index ea1ec1d..6fba801 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java
@@ -27,7 +27,7 @@
public final class MdtaMetadataEntryTest {
@Test
- public void testParcelable() {
+ public void parcelable() {
MdtaMetadataEntry mdtaMetadataEntryToParcel =
new MdtaMetadataEntry("test", new byte[] {1, 2}, 3, 4);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java
index 6ddc74c..5c1e8e1 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java
@@ -25,12 +25,12 @@
public final class Mp4ExtractorTest {
@Test
- public void testMp4Sample() throws Exception {
+ public void mp4Sample() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample.mp4");
}
@Test
- public void testMp4SampleWithSlowMotionMetadata() throws Exception {
+ public void mp4SampleWithSlowMotionMetadata() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_android_slow_motion.mp4");
}
@@ -39,12 +39,27 @@
* atom whose size indicates that it extends 8 bytes beyond the end of the file.
*/
@Test
- public void testMp4SampleWithMdatTooLong() throws Exception {
+ public void mp4SampleWithMdatTooLong() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_mdat_too_long.mp4");
}
@Test
- public void testMp4SampleWithAc4Track() throws Exception {
+ public void mp4SampleWithAc3Track() throws Exception {
+ ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_ac3.mp4");
+ }
+
+ @Test
+ public void mp4SampleWithAc4Track() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_ac4.mp4");
}
+
+ @Test
+ public void mp4SampleWithEac3Track() throws Exception {
+ ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_eac3.mp4");
+ }
+
+ @Test
+ public void mp4SampleWithEac3jocTrack() throws Exception {
+ ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_eac3joc.mp4");
+ }
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java
index 13d4529..cc5dd3c 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java
@@ -33,7 +33,7 @@
public final class PsshAtomUtilTest {
@Test
- public void testBuildPsshAtom() {
+ public void buildPsshAtom() {
byte[] schemeData = new byte[]{0, 1, 2, 3, 4, 5};
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(C.WIDEVINE_UUID, schemeData);
// Read the PSSH atom back and assert its content is as expected.
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java
index 978f12e..83aa8c6 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java
@@ -39,7 +39,7 @@
private final Random random = new Random(/* seed= */ 0);
@Test
- public void testSetupWithUnsetEndPositionFails() {
+ public void setupWithUnsetEndPositionFails() {
try {
new DefaultOggSeeker(
/* streamReader= */ new TestStreamReader(),
@@ -55,7 +55,7 @@
}
@Test
- public void testSeeking() throws Exception {
+ public void seeking() throws Exception {
byte[] data =
getByteArray(ApplicationProvider.getApplicationContext(), "ogg/random_1000_pages");
int granuleCount = 49269395;
@@ -122,7 +122,7 @@
}
@Test
- public void testSkipToNextPage() throws Exception {
+ public void skipToNextPage_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
TestUtil.joinByteArrays(
@@ -135,7 +135,7 @@
}
@Test
- public void testSkipToNextPageOverlap() throws Exception {
+ public void skipToNextPage_withOverlappingInput_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
TestUtil.joinByteArrays(
@@ -148,7 +148,7 @@
}
@Test
- public void testSkipToNextPageInputShorterThanPeekLength() throws Exception {
+ public void skipToNextPage_withInputShorterThanPeekLength_success() throws Exception {
FakeExtractorInput extractorInput =
createInput(
TestUtil.joinByteArrays(new byte[] {'x', 'O', 'g', 'g', 'S'}),
@@ -158,7 +158,7 @@
}
@Test
- public void testSkipToNextPageNoMatch() throws Exception {
+ public void skipToNextPage_withoutMatch_throwsException() throws Exception {
FakeExtractorInput extractorInput =
createInput(new byte[] {'g', 'g', 'S', 'O', 'g', 'g'}, /* simulateUnknownLength= */ false);
try {
@@ -170,7 +170,7 @@
}
@Test
- public void testReadGranuleOfLastPage() throws IOException, InterruptedException {
+ public void readGranuleOfLastPage() throws IOException {
// This test stream has three headers with granule numbers 20000, 40000 and 60000.
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/three_headers");
FakeExtractorInput input = createInput(data, /* simulateUnknownLength= */ false);
@@ -178,7 +178,7 @@
}
@Test
- public void testReadGranuleOfLastPageAfterLastHeader() throws Exception {
+ public void readGranuleOfLastPage_afterLastHeader_throwsException() throws Exception {
FakeExtractorInput input =
createInput(TestUtil.buildTestData(100, random), /* simulateUnknownLength= */ false);
try {
@@ -190,7 +190,7 @@
}
@Test
- public void testReadGranuleOfLastPageWithUnboundedLength() throws Exception {
+ public void readGranuleOfLastPage_withUnboundedLength_throwsException() throws Exception {
FakeExtractorInput input = createInput(new byte[0], /* simulateUnknownLength= */ true);
try {
assertReadGranuleOfLastPage(input, 60000);
@@ -200,8 +200,7 @@
}
}
- private static void skipToNextPage(ExtractorInput extractorInput)
- throws IOException, InterruptedException {
+ private static void skipToNextPage(ExtractorInput extractorInput) throws IOException {
DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* streamReader= */ new FlacReader(),
@@ -221,7 +220,7 @@
}
private static void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
- throws IOException, InterruptedException {
+ throws IOException {
DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* streamReader= */ new FlacReader(),
@@ -251,7 +250,7 @@
private static long seekTo(
FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule, int initialPosition)
- throws IOException, InterruptedException {
+ throws IOException {
long nextSeekPosition = initialPosition;
oggSeeker.startSeek(targetGranule);
int count = 0;
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java
index d9b2033..bffaa58 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java
@@ -33,64 +33,63 @@
private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = OggExtractor::new;
@Test
- public void testOpus() throws Exception {
+ public void opus() throws Exception {
ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus");
}
@Test
- public void testFlac() throws Exception {
+ public void flac() throws Exception {
ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg");
}
@Test
- public void testFlacNoSeektable() throws Exception {
+ public void flacNoSeektable() throws Exception {
ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg");
}
@Test
- public void testVorbis() throws Exception {
+ public void vorbis() throws Exception {
ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg");
}
@Test
- public void testSniffVorbis() throws Exception {
+ public void sniffVorbis() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/vorbis_header");
assertSniff(data, /* expectedResult= */ true);
}
@Test
- public void testSniffFlac() throws Exception {
+ public void sniffFlac() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/flac_header");
assertSniff(data, /* expectedResult= */ true);
}
@Test
- public void testSniffFailsOpusFile() throws Exception {
+ public void sniffFailsOpusFile() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/opus_header");
assertSniff(data, /* expectedResult= */ false);
}
@Test
- public void testSniffFailsInvalidOggHeader() throws Exception {
+ public void sniffFailsInvalidOggHeader() throws Exception {
byte[] data =
getByteArray(ApplicationProvider.getApplicationContext(), "ogg/invalid_ogg_header");
assertSniff(data, /* expectedResult= */ false);
}
@Test
- public void testSniffInvalidHeader() throws Exception {
+ public void sniffInvalidHeader() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/invalid_header");
assertSniff(data, /* expectedResult= */ false);
}
@Test
- public void testSniffFailsEOF() throws Exception {
+ public void sniffFailsEOF() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/eof_header");
assertSniff(data, /* expectedResult= */ false);
}
- private void assertSniff(byte[] data, boolean expectedResult)
- throws InterruptedException, IOException {
+ private void assertSniff(byte[] data, boolean expectedResult) throws IOException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(data)
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java
index 5ca99d2..492b542 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java
@@ -39,7 +39,7 @@
private final OggPacket oggPacket = new OggPacket();
@Test
- public void testReadPacketsWithEmptyPage() throws Exception {
+ public void readPacketsWithEmptyPage() throws Exception {
byte[] firstPacket = TestUtil.buildTestData(8, random);
byte[] secondPacket = TestUtil.buildTestData(272, random);
byte[] thirdPacket = TestUtil.buildTestData(256, random);
@@ -88,7 +88,7 @@
}
@Test
- public void testReadPacketWithZeroSizeTerminator() throws Exception {
+ public void readPacketWithZeroSizeTerminator() throws Exception {
byte[] firstPacket = TestUtil.buildTestData(255, random);
byte[] secondPacket = TestUtil.buildTestData(8, random);
FakeExtractorInput input =
@@ -103,7 +103,7 @@
}
@Test
- public void testReadContinuedPacketOverTwoPages() throws Exception {
+ public void readContinuedPacketOverTwoPages() throws Exception {
byte[] firstPacket = TestUtil.buildTestData(518);
FakeExtractorInput input =
createInput(
@@ -120,7 +120,7 @@
}
@Test
- public void testReadContinuedPacketOverFourPages() throws Exception {
+ public void readContinuedPacketOverFourPages() throws Exception {
byte[] firstPacket = TestUtil.buildTestData(1028);
FakeExtractorInput input =
createInput(
@@ -137,7 +137,7 @@
}
@Test
- public void testReadDiscardContinuedPacketAtStart() throws Exception {
+ public void readDiscardContinuedPacketAtStart() throws Exception {
byte[] pageBody = TestUtil.buildTestData(256 + 8);
FakeExtractorInput input =
createInput(
@@ -150,7 +150,7 @@
}
@Test
- public void testReadZeroSizedPacketsAtEndOfStream() throws Exception {
+ public void readZeroSizedPacketsAtEndOfStream() throws Exception {
byte[] firstPacket = TestUtil.buildTestData(8, random);
byte[] secondPacket = TestUtil.buildTestData(8, random);
byte[] thirdPacket = TestUtil.buildTestData(8, random);
@@ -167,7 +167,7 @@
}
@Test
- public void testParseRealFile() throws IOException, InterruptedException {
+ public void parseRealFile() throws IOException {
byte[] data = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TEST_FILE);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
int packetCounter = 0;
@@ -187,18 +187,17 @@
}
private void assertReadPacket(FakeExtractorInput extractorInput, byte[] expected)
- throws IOException, InterruptedException {
+ throws IOException {
assertThat(readPacket(extractorInput)).isTrue();
ParsableByteArray payload = oggPacket.getPayload();
assertThat(Arrays.copyOf(payload.data, payload.limit())).isEqualTo(expected);
}
- private void assertReadEof(FakeExtractorInput extractorInput)
- throws IOException, InterruptedException {
+ private void assertReadEof(FakeExtractorInput extractorInput) throws IOException {
assertThat(readPacket(extractorInput)).isFalse();
}
- private boolean readPacket(FakeExtractorInput input) throws InterruptedException, IOException {
+ private boolean readPacket(FakeExtractorInput input) throws IOException {
while (true) {
try {
return oggPacket.populate(input);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java
index 7ce346e..6b5ffe8 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java
@@ -31,7 +31,7 @@
public final class OggPageHeaderTest {
@Test
- public void testPopulatePageHeader() throws Exception {
+ public void populatePageHeader_success() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/page_header");
FakeExtractorInput input = createInput(data, /* simulateUnknownLength= */ true);
@@ -50,7 +50,8 @@
}
@Test
- public void testPopulatePageHeaderQuietOnExceptionLessThan27Bytes() throws Exception {
+ public void populatePageHeader_withLessThan27Bytes_returnFalseWithoutException()
+ throws Exception {
FakeExtractorInput input =
createInput(TestUtil.createByteArray(2, 2), /* simulateUnknownLength= */ false);
OggPageHeader header = new OggPageHeader();
@@ -58,7 +59,7 @@
}
@Test
- public void testPopulatePageHeaderQuietOnExceptionNotOgg() throws Exception {
+ public void populatePageHeader_withNotOgg_returnFalseWithoutException() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/page_header");
// change from 'O' to 'o'
data[0] = 'o';
@@ -68,7 +69,7 @@
}
@Test
- public void testPopulatePageHeaderQuiteOnExceptionWrongRevision() throws Exception {
+ public void populatePageHeader_withWrongRevision_returnFalseWithoutException() throws Exception {
byte[] data = getByteArray(ApplicationProvider.getApplicationContext(), "ogg/page_header");
// change revision from 0 to 1
data[4] = 0x01;
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java
index 5895116..c7edff7 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java
@@ -36,7 +36,7 @@
public final class VorbisReaderTest {
@Test
- public void testReadBits() throws Exception {
+ public void readBits_returnsSignificantBitsFromIndex() {
assertThat(readBits((byte) 0x00, 2, 2)).isEqualTo(0);
assertThat(readBits((byte) 0x02, 1, 1)).isEqualTo(1);
assertThat(readBits((byte) 0xF0, 4, 4)).isEqualTo(15);
@@ -44,7 +44,7 @@
}
@Test
- public void testAppendNumberOfSamples() throws Exception {
+ public void appendNumberOfSamples() {
ParsableByteArray buffer = new ParsableByteArray(4);
buffer.setLimit(0);
VorbisReader.appendNumberOfSamples(buffer, 0x01234567);
@@ -56,7 +56,7 @@
}
@Test
- public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException {
+ public void readSetupHeaders_withIOExceptions_readSuccess() throws IOException {
// initial two pages of bytes which by spec contain the three Vorbis header packets:
// identification, comment and setup header.
byte[] data =
@@ -77,8 +77,8 @@
assertThat(vorbisSetup.idHeader.data).hasLength(30);
assertThat(vorbisSetup.setupHeaderData).hasLength(3597);
- assertThat(vorbisSetup.idHeader.bitrateMax).isEqualTo(-1);
- assertThat(vorbisSetup.idHeader.bitrateMin).isEqualTo(-1);
+ assertThat(vorbisSetup.idHeader.bitrateMaximum).isEqualTo(-1);
+ assertThat(vorbisSetup.idHeader.bitrateMinimum).isEqualTo(-1);
assertThat(vorbisSetup.idHeader.bitrateNominal).isEqualTo(66666);
assertThat(vorbisSetup.idHeader.blockSize0).isEqualTo(512);
assertThat(vorbisSetup.idHeader.blockSize1).isEqualTo(1024);
@@ -98,7 +98,7 @@
}
private static VorbisSetup readSetupHeaders(VorbisReader reader, ExtractorInput input)
- throws IOException, InterruptedException {
+ throws IOException {
OggPacket oggPacket = new OggPacket();
while (true) {
try {
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java
index bd7a63e..ca7e30a 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java
@@ -27,21 +27,13 @@
public final class RawCcExtractorTest {
@Test
- public void testRawCcSample() throws Exception {
- ExtractorAsserts.assertBehavior(
- () ->
- new RawCcExtractor(
- Format.createTextContainerFormat(
- /* id= */ null,
- /* label= */ null,
- /* containerMimeType= */ null,
- /* sampleMimeType= */ MimeTypes.APPLICATION_CEA608,
- /* codecs= */ "cea608",
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null,
- /* accessibilityChannel= */ 1)),
- "rawcc/sample.rawcc");
+ public void rawCcSample() throws Exception {
+ Format format =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
+ .setCodecs("cea608")
+ .setAccessibilityChannel(1)
+ .build();
+ ExtractorAsserts.assertBehavior(() -> new RawCcExtractor(format), "rawcc/sample.rawcc");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java
index 0fe15ac..df216c7 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java
@@ -25,12 +25,17 @@
public final class Ac3ExtractorTest {
@Test
- public void testAc3Sample() throws Exception {
+ public void ac3Sample() throws Exception {
ExtractorAsserts.assertBehavior(Ac3Extractor::new, "ts/sample.ac3");
}
@Test
- public void testEAc3Sample() throws Exception {
+ public void eAc3Sample() throws Exception {
ExtractorAsserts.assertBehavior(Ac3Extractor::new, "ts/sample.eac3");
}
+
+ @Test
+ public void eAc3jocSample() throws Exception {
+ ExtractorAsserts.assertBehavior(Ac3Extractor::new, "ts/sample_eac3joc.ec3");
+ }
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java
index 3d1bafc..8ddd6f5 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java
@@ -25,7 +25,7 @@
public final class Ac4ExtractorTest {
@Test
- public void testAc4Sample() throws Exception {
+ public void ac4Sample() throws Exception {
ExtractorAsserts.assertBehavior(Ac4Extractor::new, "ts/sample.ac4");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorSeekTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorSeekTest.java
index 060f7fb..5226aa7 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorSeekTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorSeekTest.java
@@ -54,8 +54,7 @@
}
@Test
- public void testAdtsExtractorReads_returnSeekableSeekMap()
- throws IOException, InterruptedException {
+ public void adtsExtractorReads_returnSeekableSeekMap() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -74,8 +73,7 @@
}
@Test
- public void testSeeking_handlesSeekingToPositionInFile_extractsCorrectSample()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingToPositionInFile_extractsCorrectSample() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -101,8 +99,7 @@
}
@Test
- public void testSeeking_handlesSeekToEoF_extractsLastSample()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekToEoF_extractsLastSample() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -128,8 +125,7 @@
}
@Test
- public void testSeeking_handlesSeekingBackward_extractsCorrectSamples()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingBackward_extractsCorrectSamples() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -157,8 +153,7 @@
}
@Test
- public void testSeeking_handlesSeekingForward_extractsCorrectSamples()
- throws IOException, InterruptedException {
+ public void seeking_handlesSeekingForward_extractsCorrectSamples() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
@@ -186,8 +181,7 @@
}
@Test
- public void testSeeking_handlesRandomSeeks_extractsCorrectSamples()
- throws IOException, InterruptedException {
+ public void seeking_handlesRandomSeeks_extractsCorrectSamples() throws IOException {
String fileName = TEST_FILE;
Uri fileUri = TestUtil.buildAssetUri(fileName);
expectedTrackOutput =
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java
index 56776ae..a06d228 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java
@@ -25,12 +25,17 @@
public final class AdtsExtractorTest {
@Test
- public void testSample() throws Exception {
+ public void sample() throws Exception {
ExtractorAsserts.assertBehavior(AdtsExtractor::new, "ts/sample.adts");
}
@Test
- public void testSample_withSeeking() throws Exception {
+ public void sample_with_id3() throws Exception {
+ ExtractorAsserts.assertBehavior(AdtsExtractor::new, "ts/sample_with_id3.adts");
+ }
+
+ @Test
+ public void sample_withSeeking() throws Exception {
ExtractorAsserts.assertBehavior(
() -> new AdtsExtractor(/* flags= */ AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING),
"ts/sample_cbs.adts");
@@ -38,7 +43,7 @@
// https://github.com/google/ExoPlayer/issues/6700
@Test
- public void testSample_withSeekingAndTruncatedFile() throws Exception {
+ public void sample_withSeekingAndTruncatedFile() throws Exception {
ExtractorAsserts.assertBehavior(
() -> new AdtsExtractor(/* flags= */ AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING),
"ts/sample_cbs_truncated.adts");
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
index 1562475..c04c722 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java
@@ -80,7 +80,7 @@
}
@Test
- public void testSkipToNextSample() throws Exception {
+ public void skipToNextSample() throws Exception {
for (int i = 1; i <= ID3_DATA_1.length + ID3_DATA_2.length; i++) {
data.setPosition(i);
feed();
@@ -91,7 +91,7 @@
}
@Test
- public void testSkipToNextSampleResetsState() throws Exception {
+ public void skipToNextSampleResetsState() throws Exception {
data =
new ParsableByteArray(
TestUtil.joinByteArrays(
@@ -118,45 +118,45 @@
}
@Test
- public void testNoData() throws Exception {
+ public void noData() throws Exception {
feedLimited(0);
assertSampleCounts(0, 0);
}
@Test
- public void testNotEnoughDataForIdentifier() throws Exception {
+ public void notEnoughDataForIdentifier() throws Exception {
feedLimited(3 - 1);
assertSampleCounts(0, 0);
}
@Test
- public void testNotEnoughDataForHeader() throws Exception {
+ public void notEnoughDataForHeader() throws Exception {
feedLimited(10 - 1);
assertSampleCounts(0, 0);
}
@Test
- public void testNotEnoughDataForWholeId3Packet() throws Exception {
+ public void notEnoughDataForWholeId3Packet() throws Exception {
feedLimited(ID3_DATA_1.length - 1);
assertSampleCounts(0, 0);
}
@Test
- public void testConsumeWholeId3Packet() throws Exception {
+ public void consumeWholeId3Packet() throws Exception {
feedLimited(ID3_DATA_1.length);
assertSampleCounts(1, 0);
id3Output.assertSample(0, ID3_DATA_1, 0, C.BUFFER_FLAG_KEY_FRAME, null);
}
@Test
- public void testMultiId3Packet() throws Exception {
+ public void multiId3Packet() throws Exception {
feedLimited(ID3_DATA_1.length + ID3_DATA_2.length - 1);
assertSampleCounts(1, 0);
id3Output.assertSample(0, ID3_DATA_1, 0, C.BUFFER_FLAG_KEY_FRAME, null);
}
@Test
- public void testMultiId3PacketConsumed() throws Exception {
+ public void multiId3PacketConsumed() throws Exception {
feedLimited(ID3_DATA_1.length + ID3_DATA_2.length);
assertSampleCounts(2, 0);
id3Output.assertSample(0, ID3_DATA_1, 0, C.BUFFER_FLAG_KEY_FRAME, null);
@@ -164,7 +164,7 @@
}
@Test
- public void testMultiPacketConsumed() throws Exception {
+ public void multiPacketConsumed() throws Exception {
for (int i = 0; i < 10; i++) {
data.setPosition(0);
feed();
@@ -180,7 +180,7 @@
}
@Test
- public void testAdtsDataOnly() throws ParserException {
+ public void adtsDataOnly() throws ParserException {
data.setPosition(ID3_DATA_1.length + ID3_DATA_2.length);
feed();
assertSampleCounts(0, 1);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsDurationReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsDurationReaderTest.java
index d522178..728a164 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsDurationReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsDurationReaderTest.java
@@ -42,16 +42,17 @@
}
@Test
- public void testIsDurationReadPending_returnFalseByDefault() {
+ public void isDurationReadPending_returnFalseByDefault() {
assertThat(tsDurationReader.isDurationReadFinished()).isFalse();
}
@Test
- public void testReadDuration_returnsCorrectDuration() throws IOException, InterruptedException {
+ public void readDuration_returnsCorrectDuration() throws IOException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ps"))
+ TestUtil.getByteArray(
+ ApplicationProvider.getApplicationContext(), "ts/sample_h262_mpeg_audio.ps"))
.build();
int result = Extractor.RESULT_CONTINUE;
@@ -66,12 +67,12 @@
}
@Test
- public void testReadDuration_midStream_returnsCorrectDuration()
- throws IOException, InterruptedException {
+ public void readDuration_midStream_returnsCorrectDuration() throws IOException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ps"))
+ TestUtil.getByteArray(
+ ApplicationProvider.getApplicationContext(), "ts/sample_h262_mpeg_audio.ps"))
.build();
input.setPosition(1234);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorSeekTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorSeekTest.java
index f974a86..b5eb3a5 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorSeekTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorSeekTest.java
@@ -60,7 +60,7 @@
private long totalInputLength;
@Before
- public void setUp() throws IOException, InterruptedException {
+ public void setUp() throws IOException {
expectedOutput = new FakeExtractorOutput();
positionHolder = new PositionHolder();
extractAllSamplesFromFileToExpectedOutput(
@@ -74,8 +74,7 @@
}
@Test
- public void testPsExtractorReads_nonSeekTableFile_returnSeekableSeekMap()
- throws IOException, InterruptedException {
+ public void psExtractorReads_nonSeekTableFile_returnSeekableSeekMap() throws IOException {
PsExtractor extractor = new PsExtractor();
SeekMap seekMap = extractSeekMapAndTracks(extractor, new FakeExtractorOutput());
@@ -86,8 +85,8 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
+ throws IOException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -103,7 +102,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekToEoF() throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekToEoF() throws IOException, InterruptedException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -118,8 +117,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingBackward_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingBackward_extractsCorrectFrame() throws IOException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -138,8 +136,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingForward_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingForward_extractsCorrectFrame() throws IOException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -158,8 +155,7 @@
}
@Test
- public void testHandlePendingSeek_handlesRandomSeeks_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesRandomSeeks_extractsCorrectFrame() throws IOException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -178,8 +174,8 @@
}
@Test
- public void testHandlePendingSeek_handlesRandomSeeksAfterReadingFileOnce_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesRandomSeeksAfterReadingFileOnce_extractsCorrectFrame()
+ throws IOException {
PsExtractor extractor = new PsExtractor();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput();
@@ -201,8 +197,7 @@
// Internal methods
private long readInputLength() throws IOException {
- DataSpec dataSpec =
- new DataSpec(Uri.parse("asset:///" + PS_FILE_PATH), 0, C.LENGTH_UNSET, null);
+ DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + PS_FILE_PATH));
long totalInputLength = dataSource.open(dataSpec);
Util.closeQuietly(dataSource);
return totalInputLength;
@@ -217,7 +212,7 @@
*/
private int seekToTimeUs(
PsExtractor psExtractor, SeekMap seekMap, long seekTimeUs, FakeTrackOutput trackOutput)
- throws IOException, InterruptedException {
+ throws IOException {
int numSampleBeforeSeek = trackOutput.getSampleCount();
SeekMap.SeekPoints seekPoints = seekMap.getSeekPoints(seekTimeUs);
@@ -251,7 +246,7 @@
}
private SeekMap extractSeekMapAndTracks(PsExtractor extractor, FakeExtractorOutput output)
- throws IOException, InterruptedException {
+ throws IOException {
ExtractorInput input = getExtractorInputFromPosition(0);
extractor.init(output);
int readResult = Extractor.RESULT_CONTINUE;
@@ -279,7 +274,7 @@
}
private void readInputFileOnce(PsExtractor extractor, FakeExtractorOutput extractorOutput)
- throws IOException, InterruptedException {
+ throws IOException {
extractor.init(extractorOutput);
int readResult = Extractor.RESULT_CONTINUE;
ExtractorInput input = getExtractorInputFromPosition(0);
@@ -343,14 +338,13 @@
private ExtractorInput getExtractorInputFromPosition(long position) throws IOException {
DataSpec dataSpec =
- new DataSpec(
- Uri.parse("asset:///" + PS_FILE_PATH), position, C.LENGTH_UNSET, /* key= */ null);
+ new DataSpec(Uri.parse("asset:///" + PS_FILE_PATH), position, C.LENGTH_UNSET);
dataSource.open(dataSpec);
return new DefaultExtractorInput(dataSource, position, totalInputLength);
}
private void extractAllSamplesFromFileToExpectedOutput(Context context, String fileName)
- throws IOException, InterruptedException {
+ throws IOException {
byte[] data = TestUtil.getByteArray(context, fileName);
PsExtractor extractor = new PsExtractor();
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java
index 4cf8f81..c9e9dce 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java
@@ -25,7 +25,12 @@
public final class PsExtractorTest {
@Test
- public void testSample() throws Exception {
- ExtractorAsserts.assertBehavior(PsExtractor::new, "ts/sample.ps");
+ public void sampleWithH262AndMpegAudio() throws Exception {
+ ExtractorAsserts.assertBehavior(PsExtractor::new, "ts/sample_h262_mpeg_audio.ps");
+ }
+
+ @Test
+ public void sampleWithAc3() throws Exception {
+ ExtractorAsserts.assertBehavior(PsExtractor::new, "ts/sample_ac3.ps");
}
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java
index a089751..a023a32 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java
@@ -51,7 +51,7 @@
}
@Test
- public void testSingleOnePacketSection() {
+ public void singleOnePacketSection() {
packetPayload[0] = 3;
insertTableSection(4, (byte) 99, 3);
reader.consume(new ParsableByteArray(packetPayload), FLAG_PAYLOAD_UNIT_START_INDICATOR);
@@ -59,7 +59,7 @@
}
@Test
- public void testHeaderSplitAcrossPackets() {
+ public void headerSplitAcrossPackets() {
packetPayload[0] = 3; // The first packet includes a pointer_field.
insertTableSection(4, (byte) 100, 3); // This section header spreads across both packets.
@@ -74,7 +74,7 @@
}
@Test
- public void testFiveSectionsInTwoPackets() {
+ public void fiveSectionsInTwoPackets() {
packetPayload[0] = 0; // The first packet includes a pointer_field.
insertTableSection(1, (byte) 101, 10);
insertTableSection(14, (byte) 102, 10);
@@ -94,7 +94,7 @@
}
@Test
- public void testLongSectionAcrossFourPackets() {
+ public void longSectionAcrossFourPackets() {
packetPayload[0] = 13; // The first packet includes a pointer_field.
insertTableSection(1, (byte) 106, 10); // First section. Should be skipped.
// Second section spread across four packets. Should be consumed.
@@ -124,7 +124,7 @@
}
@Test
- public void testSeek() {
+ public void seek() {
packetPayload[0] = 13; // The first packet includes a pointer_field.
insertTableSection(1, (byte) 109, 10); // First section. Should be skipped.
// Second section spread across four packets. Should be consumed.
@@ -156,7 +156,7 @@
}
@Test
- public void testCrcChecks() {
+ public void crcChecks() {
byte[] correctCrcPat = new byte[] {
(byte) 0x0, (byte) 0x0, (byte) 0xb0, (byte) 0xd, (byte) 0x0, (byte) 0x1, (byte) 0xc1,
(byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x1, (byte) 0xe1, (byte) 0x0, (byte) 0xe8,
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java
index b1531e9..7a1a49d 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java
@@ -42,12 +42,12 @@
}
@Test
- public void testIsDurationReadPending_returnFalseByDefault() {
+ public void isDurationReadPending_returnFalseByDefault() {
assertThat(tsDurationReader.isDurationReadFinished()).isFalse();
}
@Test
- public void testReadDuration_returnsCorrectDuration() throws IOException, InterruptedException {
+ public void readDuration_returnsCorrectDuration() throws IOException, InterruptedException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(
@@ -71,8 +71,7 @@
}
@Test
- public void testReadDuration_midStream_returnsCorrectDuration()
- throws IOException, InterruptedException {
+ public void readDuration_midStream_returnsCorrectDuration() throws IOException {
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorSeekTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorSeekTest.java
index 956ccc2..42e0ace 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorSeekTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorSeekTest.java
@@ -53,7 +53,7 @@
private PositionHolder positionHolder;
@Before
- public void setUp() throws IOException, InterruptedException {
+ public void setUp() throws IOException {
positionHolder = new PositionHolder();
expectedTrackOutput =
TestUtil.extractAllSamplesFromFile(
@@ -67,8 +67,7 @@
}
@Test
- public void testTsExtractorReads_nonSeekTableFile_returnSeekableSeekMap()
- throws IOException, InterruptedException {
+ public void tsExtractorReads_nonSeekTableFile_returnSeekableSeekMap() throws IOException {
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
TsExtractor extractor = new TsExtractor();
@@ -81,8 +80,8 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingToPositionInFile_extractsCorrectFrame()
+ throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -101,8 +100,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekToEoF_extractsLastFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekToEoF_extractsLastFrame() throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -122,8 +120,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingBackward_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingBackward_extractsCorrectFrame() throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -145,8 +142,7 @@
}
@Test
- public void testHandlePendingSeek_handlesSeekingForward_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesSeekingForward_extractsCorrectFrame() throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -168,8 +164,7 @@
}
@Test
- public void testHandlePendingSeek_handlesRandomSeeks_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesRandomSeeks_extractsCorrectFrame() throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -191,8 +186,8 @@
}
@Test
- public void testHandlePendingSeek_handlesRandomSeeksAfterReadingFileOnce_extractsCorrectFrame()
- throws IOException, InterruptedException {
+ public void handlePendingSeek_handlesRandomSeeksAfterReadingFileOnce_extractsCorrectFrame()
+ throws IOException {
TsExtractor extractor = new TsExtractor();
Uri fileUri = TestUtil.buildAssetUri(TEST_FILE);
@@ -217,8 +212,7 @@
// Internal methods
private void readInputFileOnce(
- TsExtractor extractor, FakeExtractorOutput extractorOutput, Uri fileUri)
- throws IOException, InterruptedException {
+ TsExtractor extractor, FakeExtractorOutput extractorOutput, Uri fileUri) throws IOException {
extractor.init(extractorOutput);
int readResult = Extractor.RESULT_CONTINUE;
ExtractorInput input = TestUtil.getExtractorInputFromPosition(dataSource, 0, fileUri);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
index 93e3f30..794df91 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
@@ -36,8 +36,6 @@
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
-import java.io.ByteArrayOutputStream;
-import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,49 +43,67 @@
@RunWith(AndroidJUnit4.class)
public final class TsExtractorTest {
- private static final int TS_PACKET_SIZE = 188;
- private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
-
@Test
- public void testSample() throws Exception {
- ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample.ts");
+ public void sampleWithH262AndMpegAudio() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_h262_mpeg_audio.ts");
}
@Test
- public void testStreamWithJunkData() throws Exception {
- Random random = new Random(0);
- byte[] fileData =
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ts");
- ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2);
- int bytesLeft = fileData.length;
-
- writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1);
- out.write(fileData, 0, TS_PACKET_SIZE * 5);
- bytesLeft -= TS_PACKET_SIZE * 5;
-
- for (int i = TS_PACKET_SIZE * 5; i < fileData.length; i += 5 * TS_PACKET_SIZE) {
- writeJunkData(out, random.nextInt(TS_PACKET_SIZE));
- int length = Math.min(5 * TS_PACKET_SIZE, bytesLeft);
- out.write(fileData, i, length);
- bytesLeft -= length;
- }
- out.write(TS_SYNC_BYTE);
- writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1);
- fileData = out.toByteArray();
-
- ExtractorAsserts.assertOutput(
- TsExtractor::new, "ts/sample.ts", fileData, ApplicationProvider.getApplicationContext());
+ public void sampleWithH264AndMpegAudio() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_h264_mpeg_audio.ts");
}
@Test
- public void testCustomPesReader() throws Exception {
+ public void sampleWithScte35() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_scte35.ts");
+ }
+
+ @Test
+ public void sampleWithAit() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_ait.ts");
+ }
+
+ @Test
+ public void sampleWithAc3() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_ac3.ts");
+ }
+
+ @Test
+ public void sampleWithAc4() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_ac4.ts");
+ }
+
+ @Test
+ public void sampleWithEac3() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_eac3.ts");
+ }
+
+ @Test
+ public void sampleWithEac3joc() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_eac3joc.ts");
+ }
+
+ @Test
+ public void sampleWithLatm() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_latm.ts");
+ }
+
+ @Test
+ public void streamWithJunkData() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ TsExtractor::new, "ts/sample_with_junk", ApplicationProvider.getApplicationContext());
+ }
+
+ @Test
+ public void customPesReader() throws Exception {
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false);
TsExtractor tsExtractor =
new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory);
FakeExtractorInput input =
new FakeExtractorInput.Builder()
.setData(
- TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ts"))
+ TestUtil.getByteArray(
+ ApplicationProvider.getApplicationContext(), "ts/sample_h262_mpeg_audio.ts"))
.setSimulateIOErrors(false)
.setSimulateUnknownLength(false)
.setSimulatePartialReads(false)
@@ -106,12 +122,17 @@
assertThat(reader.packetsRead).isEqualTo(2);
TrackOutput trackOutput = reader.getTrackOutput();
assertThat(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)).isTrue();
- assertThat(((FakeTrackOutput) trackOutput).format)
- .isEqualTo(Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0));
+ assertThat(((FakeTrackOutput) trackOutput).lastFormat)
+ .isEqualTo(
+ new Format.Builder()
+ .setId("1/257")
+ .setSampleMimeType("mime")
+ .setLanguage("und")
+ .build());
}
@Test
- public void testCustomInitialSectionReader() throws Exception {
+ public void customInitialSectionReader() throws Exception {
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
TsExtractor tsExtractor =
new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory);
@@ -136,16 +157,6 @@
assertThat(factory.sdtReader.consumedSdts).isEqualTo(2);
}
- private static void writeJunkData(ByteArrayOutputStream out, int length) {
- for (int i = 0; i < length; i++) {
- if (((byte) i) == TS_SYNC_BYTE) {
- out.write(0);
- } else {
- out.write(i);
- }
- }
- }
-
private static final class CustomTsPayloadReaderFactory implements TsPayloadReader.Factory {
private final boolean provideSdtReader;
@@ -173,8 +184,8 @@
}
}
- @Nullable
@Override
+ @Nullable
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
if (provideCustomEsReader && streamType == 3) {
esReader = new CustomEsReader(esInfo.language);
@@ -203,8 +214,11 @@
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_UNKNOWN);
output.format(
- Format.createTextSampleFormat(
- idGenerator.getFormatId(), "mime", null, 0, 0, language, null, 0));
+ new Format.Builder()
+ .setId(idGenerator.getFormatId())
+ .setSampleMimeType("mime")
+ .setLanguage(language)
+ .build());
}
@Override
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java
index 7f9549e..9287c68 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.wav;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import org.junit.Test;
@@ -25,12 +26,21 @@
public final class WavExtractorTest {
@Test
- public void testSample() throws Exception {
+ public void sample() throws Exception {
ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample.wav");
}
@Test
- public void testSampleImaAdpcm() throws Exception {
+ public void sample_withTrailingBytes_extractsSameData() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ WavExtractor::new,
+ "wav/sample_with_trailing_bytes.wav",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "wav/sample.wav");
+ }
+
+ @Test
+ public void sample_imaAdpcm() throws Exception {
ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample_ima_adpcm.wav");
}
}
diff --git a/tree/library/hls/build.gradle b/tree/library/hls/build.gradle
index df880f7..4764cf9 100644
--- a/tree/library/hls/build.gradle
+++ b/tree/library/hls/build.gradle
@@ -40,6 +40,7 @@
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation project(modulePrefix + 'library-core')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java
index 8db6166..2ba2cd8 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java
@@ -19,7 +19,6 @@
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
@@ -30,6 +29,7 @@
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.EOFException;
@@ -52,6 +52,8 @@
public static final String M4_FILE_EXTENSION_PREFIX = ".m4";
public static final String MP4_FILE_EXTENSION_PREFIX = ".mp4";
public static final String CMF_FILE_EXTENSION_PREFIX = ".cmf";
+ public static final String TS_FILE_EXTENSION = ".ts";
+ public static final String TS_FILE_EXTENSION_PREFIX = ".ts";
public static final String VTT_FILE_EXTENSION = ".vtt";
public static final String WEBVTT_FILE_EXTENSION = ".webvtt";
@@ -89,14 +91,13 @@
Uri uri,
Format format,
@Nullable List<Format> muxedCaptionFormats,
- @Nullable DrmInitData drmInitData,
TimestampAdjuster timestampAdjuster,
Map<String, List<String>> responseHeaders,
ExtractorInput extractorInput)
- throws InterruptedException, IOException {
+ throws IOException {
if (previousExtractor != null) {
- // A extractor has already been successfully used. Return one of the same type.
+ // An extractor has already been successfully used. Return one of the same type.
if (isReusable(previousExtractor)) {
return buildResult(previousExtractor);
} else {
@@ -110,16 +111,29 @@
}
// Try selecting the extractor by the file extension.
+ @Nullable
Extractor extractorByFileExtension =
- createExtractorByFileExtension(
- uri, format, muxedCaptionFormats, drmInitData, timestampAdjuster);
+ createExtractorByFileExtension(uri, format, muxedCaptionFormats, timestampAdjuster);
extractorInput.resetPeekPosition();
- if (sniffQuietly(extractorByFileExtension, extractorInput)) {
+ if (extractorByFileExtension != null
+ && sniffQuietly(extractorByFileExtension, extractorInput)) {
return buildResult(extractorByFileExtension);
}
// We need to manually sniff each known type, without retrying the one selected by file
- // extension.
+ // extension. Extractors order is optimized according to
+ // https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
+
+ // Extractor to be used if the type is not recognized.
+ @Nullable Extractor fallBackExtractor = extractorByFileExtension;
+
+ if (!(extractorByFileExtension instanceof FragmentedMp4Extractor)) {
+ FragmentedMp4Extractor fragmentedMp4Extractor =
+ createFragmentedMp4Extractor(timestampAdjuster, format, muxedCaptionFormats);
+ if (sniffQuietly(fragmentedMp4Extractor, extractorInput)) {
+ return buildResult(fragmentedMp4Extractor);
+ }
+ }
if (!(extractorByFileExtension instanceof WebvttExtractor)) {
WebvttExtractor webvttExtractor = new WebvttExtractor(format.language, timestampAdjuster);
@@ -128,6 +142,22 @@
}
}
+ if (!(extractorByFileExtension instanceof TsExtractor)) {
+ TsExtractor tsExtractor =
+ createTsExtractor(
+ payloadReaderFactoryFlags,
+ exposeCea608WhenMissingDeclarations,
+ format,
+ muxedCaptionFormats,
+ timestampAdjuster);
+ if (sniffQuietly(tsExtractor, extractorInput)) {
+ return buildResult(tsExtractor);
+ }
+ if (fallBackExtractor == null) {
+ fallBackExtractor = tsExtractor;
+ }
+ }
+
if (!(extractorByFileExtension instanceof AdtsExtractor)) {
AdtsExtractor adtsExtractor = new AdtsExtractor();
if (sniffQuietly(adtsExtractor, extractorInput)) {
@@ -157,36 +187,14 @@
}
}
- if (!(extractorByFileExtension instanceof FragmentedMp4Extractor)) {
- FragmentedMp4Extractor fragmentedMp4Extractor =
- createFragmentedMp4Extractor(timestampAdjuster, format, drmInitData, muxedCaptionFormats);
- if (sniffQuietly(fragmentedMp4Extractor, extractorInput)) {
- return buildResult(fragmentedMp4Extractor);
- }
- }
-
- if (!(extractorByFileExtension instanceof TsExtractor)) {
- TsExtractor tsExtractor =
- createTsExtractor(
- payloadReaderFactoryFlags,
- exposeCea608WhenMissingDeclarations,
- format,
- muxedCaptionFormats,
- timestampAdjuster);
- if (sniffQuietly(tsExtractor, extractorInput)) {
- return buildResult(tsExtractor);
- }
- }
-
- // Fall back on the extractor created by file extension.
- return buildResult(extractorByFileExtension);
+ return buildResult(Assertions.checkNotNull(fallBackExtractor));
}
+ @Nullable
private Extractor createExtractorByFileExtension(
Uri uri,
Format format,
@Nullable List<Format> muxedCaptionFormats,
- @Nullable DrmInitData drmInitData,
TimestampAdjuster timestampAdjuster) {
String lastPathSegment = uri.getLastPathSegment();
if (lastPathSegment == null) {
@@ -209,16 +217,17 @@
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)
|| lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)
|| lastPathSegment.startsWith(CMF_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) {
- return createFragmentedMp4Extractor(
- timestampAdjuster, format, drmInitData, muxedCaptionFormats);
- } else {
- // For any other file extension, we assume TS format.
+ return createFragmentedMp4Extractor(timestampAdjuster, format, muxedCaptionFormats);
+ } else if (lastPathSegment.endsWith(TS_FILE_EXTENSION)
+ || lastPathSegment.startsWith(TS_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)) {
return createTsExtractor(
payloadReaderFactoryFlags,
exposeCea608WhenMissingDeclarations,
format,
muxedCaptionFormats,
timestampAdjuster);
+ } else {
+ return null;
}
}
@@ -240,15 +249,11 @@
// closed caption track on channel 0.
muxedCaptionFormats =
Collections.singletonList(
- Format.createTextSampleFormat(
- /* id= */ null,
- MimeTypes.APPLICATION_CEA608,
- /* selectionFlags= */ 0,
- /* language= */ null));
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_CEA608).build());
} else {
muxedCaptionFormats = Collections.emptyList();
}
- String codecs = format.codecs;
+ @Nullable String codecs = format.codecs;
if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can
@@ -270,7 +275,6 @@
private static FragmentedMp4Extractor createFragmentedMp4Extractor(
TimestampAdjuster timestampAdjuster,
Format format,
- @Nullable DrmInitData drmInitData,
@Nullable List<Format> muxedCaptionFormats) {
// Only enable the EMSG TrackOutput if this is the 'variant' track (i.e. the main one) to avoid
// creating a separate EMSG track for every audio track in a video stream.
@@ -278,7 +282,6 @@
/* flags= */ isFmp4Variant(format) ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK : 0,
timestampAdjuster,
/* sideloadedTrack= */ null,
- drmInitData,
muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList());
}
@@ -326,7 +329,7 @@
}
private static boolean sniffQuietly(Extractor extractor, ExtractorInput input)
- throws InterruptedException, IOException {
+ throws IOException {
boolean result = false;
try {
result = extractor.sniff(input);
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
index 1a77715..0b587fd 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
@@ -512,7 +512,7 @@
return null;
}
- byte[] encryptionKey = keyCache.remove(keyUri);
+ @Nullable byte[] encryptionKey = keyCache.remove(keyUri);
if (encryptionKey != null) {
// The key was present in the key cache. We re-insert it to prevent it from being evicted by
// the following key addition. Note that removal of the key is necessary to affect the
@@ -520,7 +520,8 @@
keyCache.put(keyUri, encryptionKey);
return null;
}
- DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
+ DataSpec dataSpec =
+ new DataSpec.Builder().setUri(keyUri).setFlags(DataSpec.FLAG_ALLOW_GZIP).build();
return new EncryptionKeyChunk(
encryptionDataSource,
dataSpec,
@@ -646,8 +647,7 @@
checkInBounds();
Segment segment = playlist.segments.get((int) getCurrentIndex());
Uri chunkUri = UriUtil.resolveToUri(playlist.baseUri, segment.url);
- return new DataSpec(
- chunkUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
+ return new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength);
}
@Override
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java
index 927b798..eb3cf8b 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java
@@ -18,7 +18,6 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
@@ -71,7 +70,6 @@
* @param format A {@link Format} associated with the chunk to extract.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
- * @param drmInitData {@link DrmInitData} associated with the chunk.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param responseHeaders The HTTP response headers associated with the media segment or
* initialization section to extract.
@@ -79,7 +77,6 @@
* extractor's {@link Extractor#read(ExtractorInput, PositionHolder)}. Must only be used to
* call {@link Extractor#sniff(ExtractorInput)}.
* @return A {@link Result}.
- * @throws InterruptedException If the thread is interrupted while sniffing.
* @throws IOException If an I/O error is encountered while sniffing.
*/
Result createExtractor(
@@ -87,9 +84,8 @@
Uri uri,
Format format,
@Nullable List<Format> muxedCaptionFormats,
- @Nullable DrmInitData drmInitData,
TimestampAdjuster timestampAdjuster,
Map<String, List<String>> responseHeaders,
ExtractorInput sniffingExtractorInput)
- throws InterruptedException, IOException;
+ throws IOException;
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
index e2bc5a1..d397dc5 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
@@ -38,6 +38,7 @@
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -94,9 +95,9 @@
new DataSpec(
UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url),
mediaSegment.byterangeOffset,
- mediaSegment.byterangeLength,
- /* key= */ null);
+ mediaSegment.byterangeLength);
boolean mediaSegmentEncrypted = mediaSegmentKey != null;
+ @Nullable
byte[] mediaSegmentIv =
mediaSegmentEncrypted
? getEncryptionIvArray(Assertions.checkNotNull(mediaSegment.encryptionIV))
@@ -107,20 +108,17 @@
HlsMediaPlaylist.Segment initSegment = mediaSegment.initializationSegment;
DataSpec initDataSpec = null;
boolean initSegmentEncrypted = false;
- DataSource initDataSource = null;
+ @Nullable DataSource initDataSource = null;
if (initSegment != null) {
initSegmentEncrypted = initSegmentKey != null;
+ @Nullable
byte[] initSegmentIv =
initSegmentEncrypted
? getEncryptionIvArray(Assertions.checkNotNull(initSegment.encryptionIV))
: null;
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
initDataSpec =
- new DataSpec(
- initSegmentUri,
- initSegment.byterangeOffset,
- initSegment.byterangeLength,
- /* key= */ null);
+ new DataSpec(initSegmentUri, initSegment.byterangeOffset, initSegment.byterangeLength);
initDataSource = buildDataSource(dataSource, initSegmentKey, initSegmentIv);
}
@@ -129,7 +127,7 @@
int discontinuitySequenceNumber =
mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
- Extractor previousExtractor = null;
+ @Nullable Extractor previousExtractor = null;
Id3Decoder id3Decoder;
ParsableByteArray scratchId3Data;
boolean shouldSpliceIn;
@@ -196,6 +194,9 @@
/** The url of the playlist from which this chunk was obtained. */
public final Uri playlistUrl;
+ /** Whether the samples parsed from this chunk should be spliced into already queued samples. */
+ public final boolean shouldSpliceIn;
+
@Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec;
@Nullable private final Extractor previousExtractor;
@@ -203,7 +204,6 @@
private final boolean isMasterTimestampSource;
private final boolean hasGapTag;
private final TimestampAdjuster timestampAdjuster;
- private final boolean shouldSpliceIn;
private final HlsExtractorFactory extractorFactory;
@Nullable private final List<Format> muxedCaptionFormats;
@Nullable private final DrmInitData drmInitData;
@@ -284,7 +284,6 @@
*/
public void init(HlsSampleStreamWrapper output) {
this.output = output;
- output.init(uid, shouldSpliceIn);
}
@Override
@@ -300,7 +299,7 @@
}
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
// output == null means init() hasn't been called.
Assertions.checkNotNull(output);
if (extractor == null && previousExtractor != null) {
@@ -320,7 +319,7 @@
// Internal methods.
@RequiresNonNull("output")
- private void maybeLoadInitData() throws IOException, InterruptedException {
+ private void maybeLoadInitData() throws IOException {
if (!initDataLoadRequired) {
return;
}
@@ -333,9 +332,13 @@
}
@RequiresNonNull("output")
- private void loadMedia() throws IOException, InterruptedException {
+ private void loadMedia() throws IOException {
if (!isMasterTimestampSource) {
- timestampAdjuster.waitUntilInitialized();
+ try {
+ timestampAdjuster.waitUntilInitialized();
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException();
+ }
} else if (timestampAdjuster.getFirstSampleTimestampUs() == TimestampAdjuster.DO_NOT_OFFSET) {
// We're the master and we haven't set the desired first sample timestamp yet.
timestampAdjuster.setFirstSampleTimestampUs(startTimeUs);
@@ -350,8 +353,7 @@
*/
@RequiresNonNull("output")
private void feedDataToExtractor(
- DataSource dataSource, DataSpec dataSpec, boolean dataIsEncrypted)
- throws IOException, InterruptedException {
+ DataSource dataSource, DataSpec dataSpec, boolean dataIsEncrypted) throws IOException {
// If we previously fed part of this chunk to the extractor, we need to skip it this time. For
// encrypted content we need to skip the data by reading it through the source, so as to ensure
// correct decryption of the remainder of the chunk. For clear content, we can request the
@@ -376,7 +378,7 @@
result = extractor.read(input, DUMMY_POSITION_HOLDER);
}
} finally {
- nextLoadPosition = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
+ nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
}
} finally {
Util.closeQuietly(dataSource);
@@ -386,11 +388,10 @@
@RequiresNonNull("output")
@EnsuresNonNull("extractor")
private DefaultExtractorInput prepareExtraction(DataSource dataSource, DataSpec dataSpec)
- throws IOException, InterruptedException {
+ throws IOException {
long bytesToRead = dataSource.open(dataSpec);
-
DefaultExtractorInput extractorInput =
- new DefaultExtractorInput(dataSource, dataSpec.absoluteStreamPosition, bytesToRead);
+ new DefaultExtractorInput(dataSource, dataSpec.position, bytesToRead);
if (extractor == null) {
long id3Timestamp = peekId3PrivTimestamp(extractorInput);
@@ -402,7 +403,6 @@
dataSpec.uri,
trackFormat,
muxedCaptionFormats,
- drmInitData,
timestampAdjuster,
dataSource.getResponseHeaders(),
extractorInput);
@@ -421,21 +421,20 @@
output.onNewExtractor();
extractor.init(output);
}
-
+ output.setDrmInitData(drmInitData);
return extractorInput;
}
/**
- * Peek the presentation timestamp of the first sample in the chunk from an ID3 PRIV as defined
- * in the HLS spec, version 20, Section 3.4. Returns {@link C#TIME_UNSET} if the frame is not
- * found. This method only modifies the peek position.
+ * Peek the presentation timestamp of the first sample in the chunk from an ID3 PRIV as defined in
+ * the HLS spec, version 20, Section 3.4. Returns {@link C#TIME_UNSET} if the frame is not found.
+ * This method only modifies the peek position.
*
* @param input The {@link ExtractorInput} to obtain the PRIV frame from.
* @return The parsed, adjusted timestamp in microseconds
* @throws IOException If an error occurred peeking from the input.
- * @throws InterruptedException If the thread was interrupted.
*/
- private long peekId3PrivTimestamp(ExtractorInput input) throws IOException, InterruptedException {
+ private long peekId3PrivTimestamp(ExtractorInput input) throws IOException {
input.resetPeekPosition();
try {
input.peekFully(scratchId3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH);
@@ -517,5 +516,4 @@
}
return dataSource;
}
-
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java
index 3b723af..b6985a8 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java
@@ -67,7 +67,7 @@
private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory;
@Nullable private final TransferListener mediaTransferListener;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final EventDispatcher eventDispatcher;
private final Allocator allocator;
@@ -112,7 +112,7 @@
HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory,
@Nullable TransferListener mediaTransferListener,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
Allocator allocator,
@@ -660,18 +660,16 @@
TrackGroup id3TrackGroup =
new TrackGroup(
- Format.createSampleFormat(
- /* id= */ "ID3",
- MimeTypes.APPLICATION_ID3,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* drmInitData= */ null));
+ new Format.Builder()
+ .setId("ID3")
+ .setSampleMimeType(MimeTypes.APPLICATION_ID3)
+ .build());
muxedTrackGroups.add(id3TrackGroup);
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
muxedTrackGroups.toArray(new TrackGroup[0]),
/* primaryTrackGroupIndex= */ 0,
- /* optionalTrackGroupsIndices= */ muxedTrackGroups.indexOf(id3TrackGroup));
+ /* optionalTrackGroupsIndices...= */ muxedTrackGroups.indexOf(id3TrackGroup));
}
}
@@ -791,33 +789,34 @@
}
private static Format deriveVideoFormat(Format variantFormat) {
- String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
- String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
- return Format.createVideoContainerFormat(
- variantFormat.id,
- variantFormat.label,
- variantFormat.containerMimeType,
- sampleMimeType,
- codecs,
- variantFormat.metadata,
- variantFormat.bitrate,
- variantFormat.width,
- variantFormat.height,
- variantFormat.frameRate,
- /* initializationData= */ null,
- variantFormat.selectionFlags,
- variantFormat.roleFlags);
+ @Nullable String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
+ @Nullable String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
+ return new Format.Builder()
+ .setId(variantFormat.id)
+ .setLabel(variantFormat.label)
+ .setContainerMimeType(variantFormat.containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setCodecs(codecs)
+ .setMetadata(variantFormat.metadata)
+ .setAverageBitrate(variantFormat.averageBitrate)
+ .setPeakBitrate(variantFormat.peakBitrate)
+ .setWidth(variantFormat.width)
+ .setHeight(variantFormat.height)
+ .setFrameRate(variantFormat.frameRate)
+ .setSelectionFlags(variantFormat.selectionFlags)
+ .setRoleFlags(variantFormat.roleFlags)
+ .build();
}
private static Format deriveAudioFormat(
Format variantFormat, @Nullable Format mediaTagFormat, boolean isPrimaryTrackInVariant) {
- String codecs;
- Metadata metadata;
+ @Nullable String codecs;
+ @Nullable Metadata metadata;
int channelCount = Format.NO_VALUE;
int selectionFlags = 0;
int roleFlags = 0;
- String language = null;
- String label = null;
+ @Nullable String language = null;
+ @Nullable String label = null;
if (mediaTagFormat != null) {
codecs = mediaTagFormat.codecs;
metadata = mediaTagFormat.metadata;
@@ -837,22 +836,23 @@
label = variantFormat.label;
}
}
- String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
- int bitrate = isPrimaryTrackInVariant ? variantFormat.bitrate : Format.NO_VALUE;
- return Format.createAudioContainerFormat(
- variantFormat.id,
- label,
- variantFormat.containerMimeType,
- sampleMimeType,
- codecs,
- metadata,
- bitrate,
- channelCount,
- /* sampleRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- selectionFlags,
- roleFlags,
- language);
+ @Nullable String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
+ int averageBitrate = isPrimaryTrackInVariant ? variantFormat.averageBitrate : Format.NO_VALUE;
+ int peakBitrate = isPrimaryTrackInVariant ? variantFormat.peakBitrate : Format.NO_VALUE;
+ return new Format.Builder()
+ .setId(variantFormat.id)
+ .setLabel(label)
+ .setContainerMimeType(variantFormat.containerMimeType)
+ .setSampleMimeType(sampleMimeType)
+ .setCodecs(codecs)
+ .setMetadata(metadata)
+ .setAverageBitrate(averageBitrate)
+ .setPeakBitrate(peakBitrate)
+ .setChannelCount(channelCount)
+ .setSelectionFlags(selectionFlags)
+ .setRoleFlags(roleFlags)
+ .setLanguage(language)
+ .build();
}
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
index 96be633..b2ce33a 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.Extractor;
@@ -52,6 +53,7 @@
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
+import java.util.Collections;
import java.util.List;
/** An HLS {@link MediaSource}. */
@@ -93,12 +95,12 @@
private HlsPlaylistParserFactory playlistParserFactory;
private HlsPlaylistTracker.Factory playlistTrackerFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private DrmSessionManager<?> drmSessionManager;
+ private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private boolean allowChunklessPreparation;
@MetadataType private int metadataType;
private boolean useSessionKeys;
- @Nullable private List<StreamKey> streamKeys;
+ private List<StreamKey> streamKeys;
@Nullable private Object tag;
/**
@@ -127,16 +129,14 @@
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
metadataType = METADATA_TYPE_ID3;
+ streamKeys = Collections.emptyList();
}
/**
- * Sets a tag for the media source which will be published in the {@link
- * com.google.android.exoplayer2.Timeline} of the source as {@link
- * com.google.android.exoplayer2.Timeline.Window#tag}.
- *
- * @param tag A tag for the media source.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
@@ -290,7 +290,7 @@
* @return This factory, for convenience.
*/
@Override
- public Factory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager) {
+ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager =
drmSessionManager != null
? drmSessionManager
@@ -298,9 +298,15 @@
return this;
}
+ /**
+ * @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
+ * #createMediaSource(MediaItem)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public Factory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
- this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
+ this.streamKeys = streamKeys != null ? streamKeys : Collections.emptyList();
return this;
}
@@ -308,6 +314,7 @@
* @deprecated Use {@link #createMediaSource(Uri)} and {@link #addEventListener(Handler,
* MediaSourceEventListener)} instead.
*/
+ @SuppressWarnings("deprecation")
@Deprecated
public HlsMediaSource createMediaSource(
Uri playlistUri,
@@ -320,20 +327,35 @@
return mediaSource;
}
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public HlsMediaSource createMediaSource(Uri uri) {
+ return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
+ }
+
/**
* Returns a new {@link HlsMediaSource} using the current parameters.
*
+ * @param mediaItem The {@link MediaItem}.
* @return The new {@link HlsMediaSource}.
+ * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
- public HlsMediaSource createMediaSource(Uri playlistUri) {
+ public HlsMediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
HlsPlaylistParserFactory playlistParserFactory = this.playlistParserFactory;
- if (streamKeys != null) {
+ List<StreamKey> streamKeys =
+ !mediaItem.playbackProperties.streamKeys.isEmpty()
+ ? mediaItem.playbackProperties.streamKeys
+ : this.streamKeys;
+ if (!streamKeys.isEmpty()) {
playlistParserFactory =
new FilteringHlsPlaylistParserFactory(playlistParserFactory, streamKeys);
}
return new HlsMediaSource(
- playlistUri,
+ mediaItem.playbackProperties.sourceUri,
hlsDataSourceFactory,
extractorFactory,
compositeSequenceableLoaderFactory,
@@ -344,7 +366,7 @@
allowChunklessPreparation,
metadataType,
useSessionKeys,
- tag);
+ mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
@Override
@@ -357,7 +379,7 @@
private final Uri manifestUri;
private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final boolean allowChunklessPreparation;
private final @MetadataType int metadataType;
@@ -372,7 +394,7 @@
HlsDataSourceFactory dataSourceFactory,
HlsExtractorFactory extractorFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
HlsPlaylistTracker playlistTracker,
boolean allowChunklessPreparation,
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
index 6ace6cc..41dc652 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
@@ -29,7 +29,6 @@
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
@@ -48,11 +47,13 @@
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@@ -113,7 +114,7 @@
private final HlsChunkSource chunkSource;
private final Allocator allocator;
@Nullable private final Format muxedAudioFormat;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final Loader loader;
private final EventDispatcher eventDispatcher;
@@ -128,7 +129,7 @@
private final ArrayList<HlsSampleStream> hlsSampleStreams;
private final Map<String, DrmInitData> overridingDrmInitData;
- private SampleQueue[] sampleQueues;
+ private HlsSampleQueue[] sampleQueues;
private int[] sampleQueueTrackIds;
private Set<Integer> sampleQueueMappingDoneByType;
private SparseIntArray sampleQueueIndicesByType;
@@ -162,7 +163,8 @@
// Accessed only by the loading thread.
private boolean tracksEnded;
private long sampleOffsetUs;
- private int chunkUid;
+ @Nullable private DrmInitData drmInitData;
+ @Nullable private HlsMediaChunk sourceChunk;
/**
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
@@ -188,7 +190,7 @@
Allocator allocator,
long positionUs,
@Nullable Format muxedAudioFormat,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
@HlsMediaSource.MetadataType int metadataType) {
@@ -207,7 +209,7 @@
sampleQueueTrackIds = new int[0];
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
- sampleQueues = new SampleQueue[0];
+ sampleQueues = new HlsSampleQueue[0];
sampleQueueIsAudioVideoFlags = new boolean[0];
sampleQueuesEnabledStates = new boolean[0];
mediaChunks = new ArrayList<>();
@@ -567,7 +569,7 @@
chunkIndex < mediaChunks.size()
? mediaChunks.get(chunkIndex).trackFormat
: Assertions.checkNotNull(upstreamTrackFormat);
- format = format.copyWithManifestFormatInfo(trackFormat);
+ format = format.withManifestFormatInfo(trackFormat);
}
formatHolder.format = format;
}
@@ -666,11 +668,7 @@
}
if (isMediaChunk(loadable)) {
- pendingResetPositionUs = C.TIME_UNSET;
- HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
- mediaChunk.init(this);
- mediaChunks.add(mediaChunk);
- upstreamTrackFormat = mediaChunk.trackFormat;
+ initMediaChunkLoad((HlsMediaChunk) loadable);
}
long elapsedRealtimeMs =
loader.startLoading(
@@ -725,8 +723,8 @@
}
@Override
- public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,
- boolean released) {
+ public void onLoadCanceled(
+ Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
eventDispatcher.loadCanceled(
loadable.dataSpec,
loadable.getUri(),
@@ -817,18 +815,21 @@
// Called by the consuming thread, but only when there is no loading thread.
/**
- * Initializes the wrapper for loading a chunk.
+ * Performs initialization for a media chunk that's about to start loading.
*
- * @param chunkUid The chunk's uid.
- * @param shouldSpliceIn Whether the samples parsed from the chunk should be spliced into any
- * samples already queued to the wrapper.
+ * @param chunk The media chunk that's about to start loading.
*/
- public void init(int chunkUid, boolean shouldSpliceIn) {
- this.chunkUid = chunkUid;
- for (SampleQueue sampleQueue : sampleQueues) {
- sampleQueue.sourceId(chunkUid);
+ private void initMediaChunkLoad(HlsMediaChunk chunk) {
+ sourceChunk = chunk;
+ upstreamTrackFormat = chunk.trackFormat;
+ pendingResetPositionUs = C.TIME_UNSET;
+ mediaChunks.add(chunk);
+
+ chunk.init(this);
+ for (HlsSampleQueue sampleQueue : sampleQueues) {
+ sampleQueue.setSourceChunk(chunk);
}
- if (shouldSpliceIn) {
+ if (chunk.shouldSpliceIn) {
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.splice();
}
@@ -904,17 +905,22 @@
private SampleQueue createSampleQueue(int id, int type) {
int trackCount = sampleQueues.length;
- SampleQueue trackOutput =
- new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData);
- trackOutput.setSampleOffsetUs(sampleOffsetUs);
- trackOutput.sourceId(chunkUid);
- trackOutput.setUpstreamFormatChangeListener(this);
+ boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
+ HlsSampleQueue sampleQueue =
+ new HlsSampleQueue(allocator, drmSessionManager, eventDispatcher, overridingDrmInitData);
+ if (isAudioVideo) {
+ sampleQueue.setDrmInitData(drmInitData);
+ }
+ sampleQueue.setSampleOffsetUs(sampleOffsetUs);
+ if (sourceChunk != null) {
+ sampleQueue.setSourceChunk(sourceChunk);
+ }
+ sampleQueue.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
- sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
+ sampleQueues = Util.nullSafeArrayAppend(sampleQueues, sampleQueue);
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
- sampleQueueIsAudioVideoFlags[trackCount] =
- type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
+ sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo;
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
sampleQueueMappingDoneByType.add(type);
sampleQueueIndicesByType.append(type, trackCount);
@@ -923,7 +929,7 @@
primarySampleQueueType = type;
}
sampleQueuesEnabledStates = Arrays.copyOf(sampleQueuesEnabledStates, trackCount + 1);
- return trackOutput;
+ return sampleQueue;
}
@Override
@@ -951,10 +957,53 @@
sampleQueueMappingDoneByType.clear();
}
+ /**
+ * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples that
+ * are subsequently loaded by this wrapper.
+ *
+ * @param sampleOffsetUs The timestamp offset in microseconds.
+ */
public void setSampleOffsetUs(long sampleOffsetUs) {
- this.sampleOffsetUs = sampleOffsetUs;
- for (SampleQueue sampleQueue : sampleQueues) {
- sampleQueue.setSampleOffsetUs(sampleOffsetUs);
+ if (this.sampleOffsetUs != sampleOffsetUs) {
+ this.sampleOffsetUs = sampleOffsetUs;
+ for (SampleQueue sampleQueue : sampleQueues) {
+ sampleQueue.setSampleOffsetUs(sampleOffsetUs);
+ }
+ }
+ }
+
+ /**
+ * Sets default {@link DrmInitData} for samples that are subsequently loaded by this wrapper.
+ *
+ * <p>This method should be called prior to loading each {@link HlsMediaChunk}. The {@link
+ * DrmInitData} passed should be that of an EXT-X-KEY tag that applies to the chunk, or {@code
+ * null} otherwise.
+ *
+ * <p>The final {@link DrmInitData} for subsequently queued samples is determined as followed:
+ *
+ * <ol>
+ * <li>It is initially set to {@code drmInitData}, unless {@code drmInitData} is null in which
+ * case it's set to {@link Format#drmInitData} of the upstream {@link Format}.
+ * <li>If the initial {@link DrmInitData} is non-null and {@link #overridingDrmInitData}
+ * contains an entry whose key matches the {@link DrmInitData#schemeType}, then the sample's
+ * {@link DrmInitData} is overridden to be this entry's value.
+ * </ol>
+ *
+ * <p>
+ *
+ * @param drmInitData The default {@link DrmInitData} for samples that are subsequently queued. If
+ * non-null then it takes precedence over {@link Format#drmInitData} of the upstream {@link
+ * Format}, but will still be overridden by a matching override in {@link
+ * #overridingDrmInitData}.
+ */
+ public void setDrmInitData(@Nullable DrmInitData drmInitData) {
+ if (!Util.areEqual(this.drmInitData, drmInitData)) {
+ this.drmInitData = drmInitData;
+ for (int i = 0; i < sampleQueues.length; i++) {
+ if (sampleQueueIsAudioVideoFlags[i]) {
+ sampleQueues[i].setDrmInitData(drmInitData);
+ }
+ }
}
}
@@ -962,7 +1011,7 @@
private void updateSampleStreams(@NullableType SampleStream[] streams) {
hlsSampleStreams.clear();
- for (SampleStream stream : streams) {
+ for (@Nullable SampleStream stream : streams) {
if (stream != null) {
hlsSampleStreams.add((HlsSampleStream) stream);
}
@@ -1022,7 +1071,8 @@
for (int i = 0; i < trackGroupCount; i++) {
for (int queueIndex = 0; queueIndex < sampleQueues.length; queueIndex++) {
SampleQueue sampleQueue = sampleQueues[queueIndex];
- if (formatsMatch(sampleQueue.getUpstreamFormat(), trackGroups.get(i).getFormat(0))) {
+ Format upstreamFormat = Assertions.checkStateNotNull(sampleQueue.getUpstreamFormat());
+ if (formatsMatch(upstreamFormat, trackGroups.get(i).getFormat(0))) {
trackGroupToSampleQueueIndex[i] = queueIndex;
break;
}
@@ -1071,7 +1121,9 @@
int primaryExtractorTrackIndex = C.INDEX_UNSET;
int extractorTrackCount = sampleQueues.length;
for (int i = 0; i < extractorTrackCount; i++) {
- String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType;
+ @Nullable
+ String sampleMimeType =
+ Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat()).sampleMimeType;
int trackType;
if (MimeTypes.isVideo(sampleMimeType)) {
trackType = C.TRACK_TYPE_VIDEO;
@@ -1106,11 +1158,11 @@
// Construct the set of exposed track groups.
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
for (int i = 0; i < extractorTrackCount; i++) {
- Format sampleFormat = sampleQueues[i].getUpstreamFormat();
+ Format sampleFormat = Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat());
if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount];
if (chunkSourceTrackCount == 1) {
- formats[0] = sampleFormat.copyWithManifestFormatInfo(chunkSourceTrackGroup.getFormat(0));
+ formats[0] = sampleFormat.withManifestFormatInfo(chunkSourceTrackGroup.getFormat(0));
} else {
for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat, true);
@@ -1119,6 +1171,7 @@
trackGroups[i] = new TrackGroup(formats);
primaryTrackGroupIndex = i;
} else {
+ @Nullable
Format trackFormat =
primaryExtractorTrackType == C.TRACK_TYPE_VIDEO
&& MimeTypes.isAudio(sampleFormat.sampleMimeType)
@@ -1218,38 +1271,50 @@
*
* @param playlistFormat The format information obtained from the master playlist.
* @param sampleFormat The format information obtained from the samples.
- * @param propagateBitrate Whether the bitrate from the playlist format should be included in the
- * derived format.
+ * @param propagateBitrates Whether the bitrates from the playlist format should be included in
+ * the derived format.
* @return The derived track format.
*/
private static Format deriveFormat(
- @Nullable Format playlistFormat, Format sampleFormat, boolean propagateBitrate) {
+ @Nullable Format playlistFormat, Format sampleFormat, boolean propagateBitrates) {
if (playlistFormat == null) {
return sampleFormat;
}
- int bitrate = propagateBitrate ? playlistFormat.bitrate : Format.NO_VALUE;
- int channelCount =
- playlistFormat.channelCount != Format.NO_VALUE
- ? playlistFormat.channelCount
- : sampleFormat.channelCount;
+
int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType);
- String codecs = Util.getCodecsOfType(playlistFormat.codecs, sampleTrackType);
- String mimeType = MimeTypes.getMediaMimeType(codecs);
- if (mimeType == null) {
- mimeType = sampleFormat.sampleMimeType;
+ @Nullable String codecs = Util.getCodecsOfType(playlistFormat.codecs, sampleTrackType);
+ @Nullable String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
+
+ Format.Builder formatBuilder =
+ sampleFormat
+ .buildUpon()
+ .setId(playlistFormat.id)
+ .setLabel(playlistFormat.label)
+ .setLanguage(playlistFormat.language)
+ .setSelectionFlags(playlistFormat.selectionFlags)
+ .setAverageBitrate(propagateBitrates ? playlistFormat.averageBitrate : Format.NO_VALUE)
+ .setPeakBitrate(propagateBitrates ? playlistFormat.peakBitrate : Format.NO_VALUE)
+ .setCodecs(codecs)
+ .setWidth(playlistFormat.width)
+ .setHeight(playlistFormat.height);
+
+ if (sampleMimeType != null) {
+ formatBuilder.setSampleMimeType(sampleMimeType);
}
- return sampleFormat.copyWithContainerInfo(
- playlistFormat.id,
- playlistFormat.label,
- mimeType,
- codecs,
- playlistFormat.metadata,
- bitrate,
- playlistFormat.width,
- playlistFormat.height,
- channelCount,
- playlistFormat.selectionFlags,
- playlistFormat.language);
+
+ if (playlistFormat.channelCount != Format.NO_VALUE) {
+ formatBuilder.setChannelCount(playlistFormat.channelCount);
+ }
+
+ if (playlistFormat.metadata != null) {
+ Metadata metadata = playlistFormat.metadata;
+ if (sampleFormat.metadata != null) {
+ metadata = sampleFormat.metadata.copyWithAppendedEntriesFrom(metadata);
+ }
+ formatBuilder.setMetadata(metadata);
+ }
+
+ return formatBuilder.build();
}
private static boolean isMediaChunk(Chunk chunk) {
@@ -1257,8 +1322,8 @@
}
private static boolean formatsMatch(Format manifestFormat, Format sampleFormat) {
- String manifestFormatMimeType = manifestFormat.sampleMimeType;
- String sampleFormatMimeType = sampleFormat.sampleMimeType;
+ @Nullable String manifestFormatMimeType = manifestFormat.sampleMimeType;
+ @Nullable String sampleFormatMimeType = sampleFormat.sampleMimeType;
int manifestFormatTrackType = MimeTypes.getTrackType(manifestFormatMimeType);
if (manifestFormatTrackType != C.TRACK_TYPE_TEXT) {
return manifestFormatTrackType == MimeTypes.getTrackType(sampleFormatMimeType);
@@ -1277,28 +1342,83 @@
return new DummyTrackOutput();
}
- private static final class FormatAdjustingSampleQueue extends SampleQueue {
+ /**
+ * A {@link SampleQueue} that adds HLS specific functionality:
+ *
+ * <ul>
+ * <li>Detection of spurious discontinuities, by checking sample timestamps against the range
+ * expected for the currently loading chunk.
+ * <li>Stripping private timestamp metadata from {@link Format Formats} to avoid an excessive
+ * number of format switches in the queue.
+ * <li>Overriding of {@link Format#drmInitData}.
+ * </ul>
+ */
+ private static final class HlsSampleQueue extends SampleQueue {
+
+ /**
+ * The fraction of the chunk duration from which timestamps of samples loaded from within a
+ * chunk are allowed to deviate from the expected range.
+ */
+ private static final double MAX_TIMESTAMP_DEVIATION_FRACTION = 0.5;
+
+ /**
+ * A minimum tolerance for sample timestamps in microseconds. Timestamps of samples loaded from
+ * within a chunk are always allowed to deviate up to this amount from the expected range.
+ */
+ private static final long MIN_TIMESTAMP_DEVIATION_TOLERANCE_US = 4_000_000;
+
+ @Nullable private HlsMediaChunk sourceChunk;
+ private long sourceChunkLastSampleTimeUs;
+ private long minAllowedSampleTimeUs;
+ private long maxAllowedSampleTimeUs;
private final Map<String, DrmInitData> overridingDrmInitData;
+ @Nullable private DrmInitData drmInitData;
- public FormatAdjustingSampleQueue(
+ private HlsSampleQueue(
Allocator allocator,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
+ MediaSourceEventDispatcher eventDispatcher,
Map<String, DrmInitData> overridingDrmInitData) {
- super(allocator, drmSessionManager);
+ super(allocator, drmSessionManager, eventDispatcher);
this.overridingDrmInitData = overridingDrmInitData;
}
+ public void setSourceChunk(HlsMediaChunk chunk) {
+ sourceChunk = chunk;
+ sourceChunkLastSampleTimeUs = C.TIME_UNSET;
+ sourceId(chunk.uid);
+
+ long allowedDeviationUs =
+ Math.max(
+ (long) ((chunk.endTimeUs - chunk.startTimeUs) * MAX_TIMESTAMP_DEVIATION_FRACTION),
+ MIN_TIMESTAMP_DEVIATION_TOLERANCE_US);
+ minAllowedSampleTimeUs = chunk.startTimeUs - allowedDeviationUs;
+ maxAllowedSampleTimeUs = chunk.endTimeUs + allowedDeviationUs;
+ }
+
+ public void setDrmInitData(@Nullable DrmInitData drmInitData) {
+ this.drmInitData = drmInitData;
+ invalidateUpstreamFormatAdjustment();
+ }
+
+ @SuppressWarnings("ReferenceEquality")
@Override
- public void format(Format format) {
- DrmInitData drmInitData = format.drmInitData;
+ public Format getAdjustedUpstreamFormat(Format format) {
+ @Nullable
+ DrmInitData drmInitData = this.drmInitData != null ? this.drmInitData : format.drmInitData;
if (drmInitData != null) {
+ @Nullable
DrmInitData overridingDrmInitData = this.overridingDrmInitData.get(drmInitData.schemeType);
if (overridingDrmInitData != null) {
drmInitData = overridingDrmInitData;
}
}
- super.format(format.copyWithAdjustments(drmInitData, getAdjustedMetadata(format.metadata)));
+ @Nullable Metadata metadata = getAdjustedMetadata(format.metadata);
+ if (drmInitData != format.drmInitData || metadata != format.metadata) {
+ format = format.buildUpon().setDrmInitData(drmInitData).setMetadata(metadata).build();
+ }
+ return super.getAdjustedUpstreamFormat(format);
}
/**
@@ -1337,19 +1457,35 @@
}
return new Metadata(newMetadataEntries);
}
+
+ @Override
+ public void sampleMetadata(
+ long timeUs,
+ @C.BufferFlags int flags,
+ int size,
+ int offset,
+ @Nullable CryptoData cryptoData) {
+ // TODO: Uncomment this to reject samples with unexpected timestamps. See
+ // https://github.com/google/ExoPlayer/issues/7030.
+ // if (timeUs < minAllowedSampleTimeUs || timeUs > maxAllowedSampleTimeUs) {
+ // Util.sneakyThrow(
+ // new UnexpectedSampleTimestampException(
+ // sourceChunk, sourceChunkLastSampleTimeUs, timeUs));
+ // }
+ sourceChunkLastSampleTimeUs = timeUs;
+ super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
+ }
}
private static class EmsgUnwrappingTrackOutput implements TrackOutput {
private static final String TAG = "EmsgUnwrappingTrackOutput";
- // TODO(ibaker): Create a Formats util class with common constants like this.
+ // TODO: Create a Formats util class with common constants like this.
private static final Format ID3_FORMAT =
- Format.createSampleFormat(
- /* id= */ null, MimeTypes.APPLICATION_ID3, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_ID3).build();
private static final Format EMSG_FORMAT =
- Format.createSampleFormat(
- /* id= */ null, MimeTypes.APPLICATION_EMSG, Format.OFFSET_SAMPLE_RELATIVE);
+ new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_EMSG).build();
private final EventMessageDecoder emsgDecoder;
private final TrackOutput delegate;
@@ -1385,8 +1521,8 @@
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput)
+ throws IOException {
ensureBufferCapacity(bufferPosition + length);
int numBytesRead = input.read(buffer, bufferPosition, length);
if (numBytesRead == C.RESULT_END_OF_INPUT) {
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntry.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntry.java
index f26a9b8..9a9566b 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntry.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntry.java
@@ -19,6 +19,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.Metadata;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,8 +31,14 @@
/** Holds attributes defined in an EXT-X-STREAM-INF tag. */
public static final class VariantInfo implements Parcelable {
- /** The bitrate as declared by the EXT-X-STREAM-INF tag. */
- public final long bitrate;
+ /**
+ * The average bitrate as declared by the AVERAGE-BANDWIDTH attribute of the EXT-X-STREAM-INF
+ * tag, or {@link Format#NO_VALUE} if the attribute is not declared.
+ */
+ public final int averageBitrate;
+
+ /** The peak bitrate as declared by the BANDWIDTH attribute of the EXT-X-STREAM-INF tag. */
+ public final int peakBitrate;
/**
* The VIDEO value as defined in the EXT-X-STREAM-INF tag, or null if the VIDEO attribute is not
@@ -60,19 +67,22 @@
/**
* Creates an instance.
*
- * @param bitrate See {@link #bitrate}.
+ * @param averageBitrate See {@link #averageBitrate}.
+ * @param peakBitrate See {@link #peakBitrate}.
* @param videoGroupId See {@link #videoGroupId}.
* @param audioGroupId See {@link #audioGroupId}.
* @param subtitleGroupId See {@link #subtitleGroupId}.
* @param captionGroupId See {@link #captionGroupId}.
*/
public VariantInfo(
- long bitrate,
+ int averageBitrate,
+ int peakBitrate,
@Nullable String videoGroupId,
@Nullable String audioGroupId,
@Nullable String subtitleGroupId,
@Nullable String captionGroupId) {
- this.bitrate = bitrate;
+ this.averageBitrate = averageBitrate;
+ this.peakBitrate = peakBitrate;
this.videoGroupId = videoGroupId;
this.audioGroupId = audioGroupId;
this.subtitleGroupId = subtitleGroupId;
@@ -80,7 +90,8 @@
}
/* package */ VariantInfo(Parcel in) {
- bitrate = in.readLong();
+ averageBitrate = in.readInt();
+ peakBitrate = in.readInt();
videoGroupId = in.readString();
audioGroupId = in.readString();
subtitleGroupId = in.readString();
@@ -96,7 +107,8 @@
return false;
}
VariantInfo that = (VariantInfo) other;
- return bitrate == that.bitrate
+ return averageBitrate == that.averageBitrate
+ && peakBitrate == that.peakBitrate
&& TextUtils.equals(videoGroupId, that.videoGroupId)
&& TextUtils.equals(audioGroupId, that.audioGroupId)
&& TextUtils.equals(subtitleGroupId, that.subtitleGroupId)
@@ -105,7 +117,8 @@
@Override
public int hashCode() {
- int result = (int) (bitrate ^ (bitrate >>> 32));
+ int result = averageBitrate;
+ result = 31 * result + peakBitrate;
result = 31 * result + (videoGroupId != null ? videoGroupId.hashCode() : 0);
result = 31 * result + (audioGroupId != null ? audioGroupId.hashCode() : 0);
result = 31 * result + (subtitleGroupId != null ? subtitleGroupId.hashCode() : 0);
@@ -122,7 +135,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(bitrate);
+ dest.writeInt(averageBitrate);
+ dest.writeInt(peakBitrate);
dest.writeString(videoGroupId);
dest.writeString(audioGroupId);
dest.writeString(subtitleGroupId);
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java
new file mode 100644
index 0000000..50a1117
--- /dev/null
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/UnexpectedSampleTimestampException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source.hls;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.SampleQueue;
+import com.google.android.exoplayer2.source.chunk.MediaChunk;
+import java.io.IOException;
+
+/**
+ * Thrown when an attempt is made to write a sample to a {@link SampleQueue} whose timestamp is
+ * inconsistent with the chunk from which it originates.
+ */
+/* package */ final class UnexpectedSampleTimestampException extends IOException {
+
+ /** The {@link MediaChunk} that contained the rejected sample. */
+ public final MediaChunk mediaChunk;
+
+ /**
+ * The timestamp of the last sample that was loaded from {@link #mediaChunk} and successfully
+ * written to the {@link SampleQueue}, in microseconds. {@link C#TIME_UNSET} if the first sample
+ * in the chunk was rejected.
+ */
+ public final long lastAcceptedSampleTimeUs;
+
+ /** The timestamp of the rejected sample, in microseconds. */
+ public final long rejectedSampleTimeUs;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param mediaChunk The {@link MediaChunk} with the unexpected sample timestamp.
+ * @param lastAcceptedSampleTimeUs The timestamp of the last sample that was loaded from the chunk
+ * and successfully written to the {@link SampleQueue}, in microseconds. {@link C#TIME_UNSET}
+ * if the first sample in the chunk was rejected.
+ * @param rejectedSampleTimeUs The timestamp of the rejected sample, in microseconds.
+ */
+ public UnexpectedSampleTimestampException(
+ MediaChunk mediaChunk, long lastAcceptedSampleTimeUs, long rejectedSampleTimeUs) {
+ super(
+ "Unexpected sample timestamp: "
+ + C.usToMs(rejectedSampleTimeUs)
+ + " in chunk ["
+ + mediaChunk.startTimeUs
+ + ", "
+ + mediaChunk.endTimeUs
+ + "]");
+ this.mediaChunk = mediaChunk;
+ this.lastAcceptedSampleTimeUs = lastAcceptedSampleTimeUs;
+ this.rejectedSampleTimeUs = rejectedSampleTimeUs;
+ }
+}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java
index a62e135..6a39000 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java
@@ -49,7 +49,7 @@
public final class WebvttExtractor implements Extractor {
private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)");
- private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)");
+ private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(-?\\d+)");
private static final int HEADER_MIN_LENGTH = 6 /* "WEBVTT" */;
private static final int HEADER_MAX_LENGTH = 3 /* optional Byte Order Mark */ + HEADER_MIN_LENGTH;
@@ -72,7 +72,7 @@
// Extractor implementation.
@Override
- public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
+ public boolean sniff(ExtractorInput input) throws IOException {
// Check whether there is a header without BOM.
input.peekFully(
sampleData, /* offset= */ 0, /* length= */ HEADER_MIN_LENGTH, /* allowEndOfInput= */ false);
@@ -108,8 +108,7 @@
}
@Override
- public int read(ExtractorInput input, PositionHolder seekPosition)
- throws IOException, InterruptedException {
+ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
// output == null suggests init() hasn't been called
Assertions.checkNotNull(output);
int currentFileSize = (int) input.getLength();
@@ -158,8 +157,12 @@
if (!mediaTimestampMatcher.find()) {
throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line);
}
- vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1));
- tsTimestampUs = TimestampAdjuster.ptsToUs(Long.parseLong(mediaTimestampMatcher.group(1)));
+ vttTimestampUs =
+ WebvttParserUtil.parseTimestampUs(
+ Assertions.checkNotNull(localTimestampMatcher.group(1)));
+ tsTimestampUs =
+ TimestampAdjuster.ptsToUs(
+ Long.parseLong(Assertions.checkNotNull(mediaTimestampMatcher.group(1))));
}
}
@@ -171,7 +174,8 @@
return;
}
- long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1));
+ long firstCueTimeUs =
+ WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(cueHeaderMatcher.group(1)));
long sampleTimeUs = timestampAdjuster.adjustTsTimestamp(
TimestampAdjuster.usToPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs));
long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs;
@@ -186,8 +190,12 @@
@RequiresNonNull("output")
private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_TEXT);
- trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, null,
- Format.NO_VALUE, 0, language, null, subsampleOffsetUs));
+ trackOutput.format(
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.TEXT_VTT)
+ .setLanguage(language)
+ .setSubsampleOffsetUs(subsampleOffsetUs)
+ .build());
output.endTracks();
return trackOutput;
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
index 6e6d0af..7bf982b 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
@@ -141,8 +141,7 @@
}
}
Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
- DataSpec dataSpec =
- new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
+ DataSpec dataSpec = new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength);
out.add(new Segment(startTimeUs, dataSpec));
}
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java
index f96c7df..72f6a36 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java
@@ -102,16 +102,7 @@
*/
public static Variant createMediaPlaylistVariantUrl(Uri url) {
Format format =
- Format.createContainerFormat(
- "0",
- /* label= */ null,
- MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ null,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ new Format.Builder().setId("0").setContainerMimeType(MimeTypes.APPLICATION_M3U8).build();
return new Variant(
url,
format,
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
index 993ce8e..8a0d190 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
@@ -303,8 +303,7 @@
}
} else if (line.startsWith(TAG_STREAM_INF)) {
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
- int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
- // TODO: Plumb this into Format.
+ int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions);
String resolutionString =
@@ -344,32 +343,33 @@
iterator.next(), variableDefinitions); // #EXT-X-STREAM-INF's URI.
Uri uri = UriUtil.resolveToUri(baseUri, line);
Format format =
- Format.createVideoContainerFormat(
- /* id= */ Integer.toString(variants.size()),
- /* label= */ null,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ null,
- codecs,
- /* metadata= */ null,
- bitrate,
- width,
- height,
- frameRate,
- /* initializationData= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0);
+ new Format.Builder()
+ .setId(variants.size())
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setCodecs(codecs)
+ .setAverageBitrate(averageBitrate)
+ .setPeakBitrate(peakBitrate)
+ .setWidth(width)
+ .setHeight(height)
+ .setFrameRate(frameRate)
+ .build();
Variant variant =
new Variant(
uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId);
variants.add(variant);
- ArrayList<VariantInfo> variantInfosForUrl = urlToVariantInfos.get(uri);
+ @Nullable ArrayList<VariantInfo> variantInfosForUrl = urlToVariantInfos.get(uri);
if (variantInfosForUrl == null) {
variantInfosForUrl = new ArrayList<>();
urlToVariantInfos.put(uri, variantInfosForUrl);
}
variantInfosForUrl.add(
new VariantInfo(
- bitrate, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId));
+ averageBitrate,
+ peakBitrate,
+ videoGroupId,
+ audioGroupId,
+ subtitlesGroupId,
+ closedCaptionsGroupId));
}
}
@@ -385,9 +385,9 @@
/* groupId= */ null,
/* name= */ null,
Assertions.checkNotNull(urlToVariantInfos.get(variant.url)));
- deduplicatedVariants.add(
- variant.copyWithFormat(
- variant.format.copyWithMetadata(new Metadata(hlsMetadataEntry))));
+ Metadata metadata = new Metadata(hlsMetadataEntry);
+ Format format = variant.format.buildUpon().setMetadata(metadata).build();
+ deduplicatedVariants.add(variant.copyWithFormat(format));
}
}
@@ -395,131 +395,100 @@
line = mediaTags.get(i);
String groupId = parseStringAttr(line, REGEX_GROUP_ID, variableDefinitions);
String name = parseStringAttr(line, REGEX_NAME, variableDefinitions);
- String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions);
- Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri);
- String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions);
- @C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
- @C.RoleFlags int roleFlags = parseRoleFlags(line, variableDefinitions);
- String formatId = groupId + ":" + name;
- Format format;
+ Format.Builder formatBuilder =
+ new Format.Builder()
+ .setId(groupId + ":" + name)
+ .setLabel(name)
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setSelectionFlags(parseSelectionFlags(line))
+ .setRoleFlags(parseRoleFlags(line, variableDefinitions))
+ .setLanguage(parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions));
+
+ @Nullable String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions);
+ @Nullable Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri);
Metadata metadata =
new Metadata(new HlsTrackMetadataEntry(groupId, name, Collections.emptyList()));
switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
case TYPE_VIDEO:
- Variant variant = getVariantWithVideoGroup(variants, groupId);
- String codecs = null;
- int width = Format.NO_VALUE;
- int height = Format.NO_VALUE;
- float frameRate = Format.NO_VALUE;
+ @Nullable Variant variant = getVariantWithVideoGroup(variants, groupId);
if (variant != null) {
Format variantFormat = variant.format;
- codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
- width = variantFormat.width;
- height = variantFormat.height;
- frameRate = variantFormat.frameRate;
+ @Nullable
+ String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
+ formatBuilder
+ .setCodecs(codecs)
+ .setSampleMimeType(MimeTypes.getMediaMimeType(codecs))
+ .setWidth(variantFormat.width)
+ .setHeight(variantFormat.height)
+ .setFrameRate(variantFormat.frameRate);
}
- String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
- format =
- Format.createVideoContainerFormat(
- /* id= */ formatId,
- /* label= */ name,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- /* bitrate= */ Format.NO_VALUE,
- width,
- height,
- frameRate,
- /* initializationData= */ null,
- selectionFlags,
- roleFlags)
- .copyWithMetadata(metadata);
if (uri == null) {
// TODO: Remove this case and add a Rendition with a null uri to videos.
} else {
- videos.add(new Rendition(uri, format, groupId, name));
+ formatBuilder.setMetadata(metadata);
+ videos.add(new Rendition(uri, formatBuilder.build(), groupId, name));
}
break;
case TYPE_AUDIO:
+ @Nullable String sampleMimeType = null;
variant = getVariantWithAudioGroup(variants, groupId);
- codecs =
- variant != null
- ? Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO)
- : null;
- sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
+ if (variant != null) {
+ @Nullable
+ String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO);
+ formatBuilder.setCodecs(codecs);
+ sampleMimeType = MimeTypes.getMediaMimeType(codecs);
+ }
+ @Nullable
String channelsString =
parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions);
- int channelCount = Format.NO_VALUE;
if (channelsString != null) {
- channelCount = Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]);
+ int channelCount = Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]);
+ formatBuilder.setChannelCount(channelCount);
if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType) && channelsString.endsWith("/JOC")) {
sampleMimeType = MimeTypes.AUDIO_E_AC3_JOC;
}
}
- format =
- Format.createAudioContainerFormat(
- /* id= */ formatId,
- /* label= */ name,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- sampleMimeType,
- codecs,
- /* metadata= */ null,
- /* bitrate= */ Format.NO_VALUE,
- channelCount,
- /* sampleRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- selectionFlags,
- roleFlags,
- language);
+ formatBuilder.setSampleMimeType(sampleMimeType);
if (uri == null) {
// TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios.
- muxedAudioFormat = format;
+ muxedAudioFormat = formatBuilder.build();
} else {
- audios.add(new Rendition(uri, format.copyWithMetadata(metadata), groupId, name));
+ formatBuilder.setMetadata(metadata);
+ audios.add(new Rendition(uri, formatBuilder.build(), groupId, name));
}
break;
case TYPE_SUBTITLES:
- format =
- Format.createTextContainerFormat(
- /* id= */ formatId,
- /* label= */ name,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ MimeTypes.TEXT_VTT,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- selectionFlags,
- roleFlags,
- language)
- .copyWithMetadata(metadata);
- subtitles.add(new Rendition(uri, format, groupId, name));
+ sampleMimeType = null;
+ variant = getVariantWithSubtitleGroup(variants, groupId);
+ if (variant != null) {
+ @Nullable
+ String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_TEXT);
+ formatBuilder.setCodecs(codecs);
+ sampleMimeType = MimeTypes.getMediaMimeType(codecs);
+ }
+ if (sampleMimeType == null) {
+ sampleMimeType = MimeTypes.TEXT_VTT;
+ }
+ formatBuilder.setSampleMimeType(sampleMimeType).setMetadata(metadata);
+ subtitles.add(new Rendition(uri, formatBuilder.build(), groupId, name));
break;
case TYPE_CLOSED_CAPTIONS:
String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions);
- String mimeType;
int accessibilityChannel;
if (instreamId.startsWith("CC")) {
- mimeType = MimeTypes.APPLICATION_CEA608;
+ sampleMimeType = MimeTypes.APPLICATION_CEA608;
accessibilityChannel = Integer.parseInt(instreamId.substring(2));
} else /* starts with SERVICE */ {
- mimeType = MimeTypes.APPLICATION_CEA708;
+ sampleMimeType = MimeTypes.APPLICATION_CEA708;
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
}
if (muxedCaptionFormats == null) {
muxedCaptionFormats = new ArrayList<>();
}
- muxedCaptionFormats.add(
- Format.createTextContainerFormat(
- /* id= */ formatId,
- /* label= */ name,
- /* containerMimeType= */ null,
- /* sampleMimeType= */ mimeType,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- selectionFlags,
- roleFlags,
- language,
- accessibilityChannel));
+ formatBuilder
+ .setSampleMimeType(sampleMimeType)
+ .setAccessibilityChannel(accessibilityChannel);
+ muxedCaptionFormats.add(formatBuilder.build());
// TODO: Remove muxedCaptionFormats and add a Rendition with a null uri to closedCaptions.
break;
default:
@@ -569,6 +538,17 @@
return null;
}
+ @Nullable
+ private static Variant getVariantWithSubtitleGroup(ArrayList<Variant> variants, String groupId) {
+ for (int i = 0; i < variants.size(); i++) {
+ Variant variant = variants.get(i);
+ if (groupId.equals(variant.subtitleGroupId)) {
+ return variant;
+ }
+ }
+ return null;
+ }
+
private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
@@ -868,7 +848,7 @@
private static int parseOptionalIntAttr(String line, Pattern pattern, int defaultValue) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
- return Integer.parseInt(matcher.group(1));
+ return Integer.parseInt(Assertions.checkNotNull(matcher.group(1)));
}
return defaultValue;
}
@@ -903,7 +883,8 @@
@PolyNull String defaultValue,
Map<String, String> variableDefinitions) {
Matcher matcher = pattern.matcher(line);
- String value = matcher.find() ? matcher.group(1) : defaultValue;
+ @PolyNull
+ String value = matcher.find() ? Assertions.checkNotNull(matcher.group(1)) : defaultValue;
return variableDefinitions.isEmpty() || value == null
? value
: replaceVariableReferences(value, variableDefinitions);
@@ -931,7 +912,7 @@
String line, Pattern pattern, boolean defaultValue) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
- return matcher.group(1).equals(BOOLEAN_TRUE);
+ return BOOLEAN_TRUE.equals(matcher.group(1));
}
return defaultValue;
}
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/DefaultMediaSourceFactoryTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/DefaultMediaSourceFactoryTest.java
new file mode 100644
index 0000000..06f4dfa
--- /dev/null
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/DefaultMediaSourceFactoryTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source.hls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.util.MimeTypes;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for creating HLS media sources with the {@link DefaultMediaSourceFactory}. */
+@RunWith(AndroidJUnit4.class)
+public class DefaultMediaSourceFactoryTest {
+
+ private static final String URI_MEDIA = "http://exoplayer.dev/video";
+
+ @Test
+ public void createMediaSource_withMimeType_hlsSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_M3U8)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(HlsMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withTag_tagInSource() {
+ Object tag = new Object();
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_M3U8)
+ .setTag(tag)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource.getTag()).isEqualTo(tag);
+ }
+
+ @Test
+ public void createMediaSource_withPath_hlsSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.m3u8").build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(HlsMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withNull_usesNonNullDefaults() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.m3u8").build();
+
+ MediaSource mediaSource =
+ defaultMediaSourceFactory
+ .setDrmSessionManager(null)
+ .setDrmHttpDataSourceFactory(null)
+ .setLoadErrorHandlingPolicy(null)
+ .createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isNotNull();
+ }
+
+ @Test
+ public void getSupportedTypes_hlsModule_containsTypeHls() {
+ int[] supportedTypes =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext())
+ .getSupportedTypes();
+
+ assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER, C.TYPE_HLS);
+ }
+}
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java
index 820c39c..fe42ebb 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriodTest.java
@@ -55,11 +55,11 @@
HlsMasterPlaylist testMasterPlaylist =
createMasterPlaylist(
/* variants= */ Arrays.asList(
- createAudioOnlyVariant(/* bitrate= */ 10000),
- createMuxedVideoAudioVariant(/* bitrate= */ 200000),
- createAudioOnlyVariant(/* bitrate= */ 300000),
- createMuxedVideoAudioVariant(/* bitrate= */ 400000),
- createMuxedVideoAudioVariant(/* bitrate= */ 600000)),
+ createAudioOnlyVariant(/* peakBitrate= */ 10000),
+ createMuxedVideoAudioVariant(/* peakBitrate= */ 200000),
+ createAudioOnlyVariant(/* peakBitrate= */ 300000),
+ createMuxedVideoAudioVariant(/* peakBitrate= */ 400000),
+ createMuxedVideoAudioVariant(/* peakBitrate= */ 600000)),
/* audios= */ Arrays.asList(
createAudioRendition(/* language= */ "spa"),
createAudioRendition(/* language= */ "ger"),
@@ -121,40 +121,22 @@
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
- private static Variant createMuxedVideoAudioVariant(int bitrate) {
+ private static Variant createMuxedVideoAudioVariant(int peakBitrate) {
return createVariant(
- Format.createVideoContainerFormat(
- /* id= */ null,
- /* label= */ null,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ null,
- /* codecs= */ "avc1.100.41,mp4a.40.2",
- /* metadata= */ null,
- bitrate,
- /* width= */ Format.NO_VALUE,
- /* height= */ Format.NO_VALUE,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0));
+ new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setCodecs("avc1.100.41,mp4a.40.2")
+ .setPeakBitrate(peakBitrate)
+ .build());
}
- private static Variant createAudioOnlyVariant(int bitrate) {
+ private static Variant createAudioOnlyVariant(int peakBitrate) {
return createVariant(
- Format.createVideoContainerFormat(
- /* id= */ null,
- /* label= */ null,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ null,
- /* codecs= */ "mp4a.40.2",
- /* metadata= */ null,
- bitrate,
- /* width= */ Format.NO_VALUE,
- /* height= */ Format.NO_VALUE,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0));
+ new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setCodecs("mp4a.40.2")
+ .setPeakBitrate(peakBitrate)
+ .build());
}
private static Rendition createAudioRendition(String language) {
@@ -174,32 +156,19 @@
}
private static Format createAudioFormat(String language) {
- return Format.createAudioContainerFormat(
- /* id= */ null,
- /* label= */ null,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- MimeTypes.getMediaMimeType("mp4a.40.2"),
- /* codecs= */ "mp4a.40.2",
- /* metadata= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* channelCount= */ Format.NO_VALUE,
- /* sampleRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setSampleMimeType(MimeTypes.getMediaMimeType("mp4a.40.2"))
+ .setCodecs("mp4a.40.2")
+ .setLanguage(language)
+ .build();
}
private static Format createSubtitleFormat(String language) {
- return Format.createTextContainerFormat(
- /* id= */ null,
- /* label= */ null,
- /* containerMimeType= */ MimeTypes.APPLICATION_M3U8,
- /* sampleMimeType= */ MimeTypes.TEXT_VTT,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_M3U8)
+ .setSampleMimeType(MimeTypes.TEXT_VTT)
+ .setLanguage(language)
+ .build();
}
}
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntryTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntryTest.java
new file mode 100644
index 0000000..987c2bd
--- /dev/null
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsTrackMetadataEntryTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source.hls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test for {@link HlsTrackMetadataEntry}. */
+@RunWith(AndroidJUnit4.class)
+public class HlsTrackMetadataEntryTest {
+
+ @Test
+ public void variantInfo_parcelRoundTrip_isEqual() {
+ VariantInfo variantInfoToParcel =
+ new VariantInfo(
+ /* averageBitrate= */ 1024,
+ /* peakBitrate= */ 2048,
+ "videoGroupId",
+ "audioGroupId",
+ "subtitleGroupId",
+ "captionGroupId");
+
+ Parcel parcel = Parcel.obtain();
+ variantInfoToParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ VariantInfo variantInfoFromParcel = VariantInfo.CREATOR.createFromParcel(parcel);
+ assertThat(variantInfoFromParcel).isEqualTo(variantInfoToParcel);
+
+ parcel.recycle();
+ }
+}
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java
index 4f7e26f..2f7f8e3 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java
@@ -31,39 +31,39 @@
public class WebvttExtractorTest {
@Test
- public void sniff_sniffsWebvttHeaderWithTrailingSpace() throws IOException, InterruptedException {
+ public void sniff_sniffsWebvttHeaderWithTrailingSpace() throws IOException {
byte[] data = new byte[] {'W', 'E', 'B', 'V', 'T', 'T', ' ', '\t'};
assertThat(sniffData(data)).isTrue();
}
@Test
- public void sniff_discardsByteOrderMark() throws IOException, InterruptedException {
+ public void sniff_discardsByteOrderMark() throws IOException {
byte[] data =
new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF, 'W', 'E', 'B', 'V', 'T', 'T', '\n', ' '};
assertThat(sniffData(data)).isTrue();
}
@Test
- public void sniff_failsForIncorrectBom() throws IOException, InterruptedException {
+ public void sniff_failsForIncorrectBom() throws IOException {
byte[] data =
new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBB, 'W', 'E', 'B', 'V', 'T', 'T', '\n'};
assertThat(sniffData(data)).isFalse();
}
@Test
- public void sniff_failsForIncompleteHeader() throws IOException, InterruptedException {
+ public void sniff_failsForIncompleteHeader() throws IOException {
byte[] data = new byte[] {'W', 'E', 'B', 'V', 'T', '\n'};
assertThat(sniffData(data)).isFalse();
}
@Test
- public void sniff_failsForIncorrectHeader() throws IOException, InterruptedException {
+ public void sniff_failsForIncorrectHeader() throws IOException {
byte[] data =
new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF, 'W', 'e', 'B', 'V', 'T', 'T', '\n'};
assertThat(sniffData(data)).isFalse();
}
- private static boolean sniffData(byte[] data) throws IOException, InterruptedException {
+ private static boolean sniffData(byte[] data) throws IOException {
ExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
try {
return new WebvttExtractor(/* language= */ null, new TimestampAdjuster(0)).sniff(input);
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/DownloadHelperTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/DownloadHelperTest.java
index 3c81074..f1d0b8a 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/DownloadHelperTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/DownloadHelperTest.java
@@ -34,11 +34,11 @@
ApplicationProvider.getApplicationContext(),
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0]);
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0]);
DownloadHelper.forHls(
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0],
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0],
/* drmSessionManager= */ null,
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
}
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java
index d06d047..a8473b8 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java
@@ -96,7 +96,7 @@
}
@Test
- public void testCreateWithDefaultDownloaderFactory() {
+ public void createWithDefaultDownloaderFactory() {
DownloaderConstructorHelper constructorHelper =
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
@@ -114,7 +114,7 @@
}
@Test
- public void testCounterMethods() throws Exception {
+ public void counterMethods() throws Exception {
HlsDownloader downloader =
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
downloader.download(progressListener);
@@ -123,7 +123,7 @@
}
@Test
- public void testDownloadRepresentation() throws Exception {
+ public void downloadRepresentation() throws Exception {
HlsDownloader downloader =
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
downloader.download(progressListener);
@@ -140,7 +140,7 @@
}
@Test
- public void testDownloadMultipleRepresentations() throws Exception {
+ public void downloadMultipleRepresentations() throws Exception {
HlsDownloader downloader =
getHlsDownloader(
MASTER_PLAYLIST_URI,
@@ -151,7 +151,7 @@
}
@Test
- public void testDownloadAllRepresentations() throws Exception {
+ public void downloadAllRepresentations() throws Exception {
// Add data for the rest of the playlists
fakeDataSet
.setData(MEDIA_PLAYLIST_0_URI, MEDIA_PLAYLIST_DATA)
@@ -170,7 +170,7 @@
}
@Test
- public void testRemove() throws Exception {
+ public void remove() throws Exception {
HlsDownloader downloader =
getHlsDownloader(
MASTER_PLAYLIST_URI,
@@ -182,7 +182,7 @@
}
@Test
- public void testDownloadMediaPlaylist() throws Exception {
+ public void downloadMediaPlaylist() throws Exception {
HlsDownloader downloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI, getKeys());
downloader.download(progressListener);
@@ -197,7 +197,7 @@
}
@Test
- public void testDownloadEncMediaPlaylist() throws Exception {
+ public void downloadEncMediaPlaylist() throws Exception {
fakeDataSet =
new FakeDataSet()
.setData(ENC_MEDIA_PLAYLIST_URI, ENC_MEDIA_PLAYLIST_DATA)
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
index 0aa78d9..55aa4b3 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
@@ -35,7 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Test for {@link HlsMasterPlaylistParserTest}. */
+/** Test for {@link HlsMasterPlaylist}. */
@RunWith(AndroidJUnit4.class)
public class HlsMasterPlaylistParserTest {
@@ -194,8 +194,21 @@
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,"
+ "GROUP-ID=\"sub1\",NAME=\"English\",URI=\"s1/en/prog_index.m3u8\"\n";
+ private static final String PLAYLIST_WITH_TTML_SUBTITLE =
+ " #EXTM3U\n"
+ + "\n"
+ + "#EXT-X-VERSION:6\n"
+ + "\n"
+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"stpp.ttml.im1t,mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
+ + "http://example.com/low.m3u8\n"
+ + "\n"
+ + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",NAME=\"English\",URI=\"a1/index.m3u8\"\n"
+ + "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"s1/en/prog_index.m3u8\"\n";
+
@Test
- public void testParseMasterPlaylist() throws IOException {
+ public void parseMasterPlaylist_withSimple_success() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
List<HlsMasterPlaylist.Variant> variants = masterPlaylist.variants;
@@ -236,7 +249,7 @@
}
@Test
- public void testMasterPlaylistWithBandwdithAverage() throws IOException {
+ public void parseMasterPlaylist_withAverageBandwidth_success() throws IOException {
HlsMasterPlaylist masterPlaylist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH);
@@ -247,7 +260,7 @@
}
@Test
- public void testPlaylistWithInvalidHeader() throws IOException {
+ public void parseMasterPlaylist_withInvalidHeader_throwsException() throws IOException {
try {
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER);
fail("Expected exception not thrown.");
@@ -257,7 +270,7 @@
}
@Test
- public void testPlaylistWithClosedCaption() throws IOException {
+ public void parseMasterPlaylist_withClosedCaption_success() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
assertThat(playlist.muxedCaptionFormats).hasSize(1);
Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0);
@@ -267,7 +280,7 @@
}
@Test
- public void testPlaylistWithChannelsAttribute() throws IOException {
+ public void parseMasterPlaylist_withChannelsAttribute_success() throws IOException {
HlsMasterPlaylist playlist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CHANNELS_ATTRIBUTE);
List<HlsMasterPlaylist.Rendition> audios = playlist.audios;
@@ -278,13 +291,13 @@
}
@Test
- public void testPlaylistWithoutClosedCaptions() throws IOException {
+ public void parseMasterPlaylist_withoutClosedCaption_success() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);
assertThat(playlist.muxedCaptionFormats).isEmpty();
}
@Test
- public void testCodecPropagation() throws IOException {
+ public void parseMasterPlaylist_withAudio_codecPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
Format firstAudioFormat = playlist.audios.get(0).format;
@@ -297,7 +310,7 @@
}
@Test
- public void testAudioIdPropagation() throws IOException {
+ public void parseMasterPlaylist_withAudio_audioIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
Format firstAudioFormat = playlist.audios.get(0).format;
@@ -308,7 +321,7 @@
}
@Test
- public void testCCIdPropagation() throws IOException {
+ public void parseMasterPlaylist_withCc_cCIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
Format firstTextFormat = playlist.muxedCaptionFormats.get(0);
@@ -316,15 +329,17 @@
}
@Test
- public void testSubtitleIdPropagation() throws IOException {
+ public void parseMasterPlaylist_withSubtitles_subtitlesIdPropagated() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES);
Format firstTextFormat = playlist.subtitles.get(0).format;
assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
+ assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_VTT);
}
@Test
- public void testIndependentSegments() throws IOException {
+ public void parseMasterPlaylist_withIndependentSegments_hasNoIndenpendentSegments()
+ throws IOException {
HlsMasterPlaylist playlistWithIndependentSegments =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INDEPENDENT_SEGMENTS);
assertThat(playlistWithIndependentSegments.hasIndependentSegments).isTrue();
@@ -335,7 +350,7 @@
}
@Test
- public void testVariableSubstitution() throws IOException {
+ public void parseMasterPlaylist_withVariableSubstitution_success() throws IOException {
HlsMasterPlaylist playlistWithSubstitutions =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
HlsMasterPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0);
@@ -345,31 +360,43 @@
}
@Test
- public void testHlsMetadata() throws IOException {
+ public void parseMasterPlaylist_withTtmlSubtitle() throws IOException {
+ HlsMasterPlaylist playlistWithTtmlSubtitle =
+ parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE);
+ HlsMasterPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0);
+ Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format;
+ assertThat(firstTextFormat.id).isEqualTo("sub1:English");
+ assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8);
+ assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
+ assertThat(variant.format.codecs).isEqualTo("stpp.ttml.im1t,mp4a.40.2,avc1.66.30");
+ }
+
+ @Test
+ public void parseMasterPlaylist_withMatchingStreamInfUrls_success() throws IOException {
HlsMasterPlaylist playlist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MATCHING_STREAM_INF_URLS);
assertThat(playlist.variants).hasSize(4);
assertThat(playlist.variants.get(0).format.metadata)
.isEqualTo(
createExtXStreamInfMetadata(
- createVariantInfo(/* bitrate= */ 2227464, /* audioGroupId= */ "aud1"),
- createVariantInfo(/* bitrate= */ 2448841, /* audioGroupId= */ "aud2"),
- createVariantInfo(/* bitrate= */ 2256841, /* audioGroupId= */ "aud3")));
+ createVariantInfo(/* peakBitrate= */ 2227464, /* audioGroupId= */ "aud1"),
+ createVariantInfo(/* peakBitrate= */ 2448841, /* audioGroupId= */ "aud2"),
+ createVariantInfo(/* peakBitrate= */ 2256841, /* audioGroupId= */ "aud3")));
assertThat(playlist.variants.get(1).format.metadata)
.isEqualTo(
createExtXStreamInfMetadata(
- createVariantInfo(/* bitrate= */ 6453202, /* audioGroupId= */ "aud1"),
- createVariantInfo(/* bitrate= */ 6482579, /* audioGroupId= */ "aud3")));
+ createVariantInfo(/* peakBitrate= */ 6453202, /* audioGroupId= */ "aud1"),
+ createVariantInfo(/* peakBitrate= */ 6482579, /* audioGroupId= */ "aud3")));
assertThat(playlist.variants.get(2).format.metadata)
.isEqualTo(
createExtXStreamInfMetadata(
- createVariantInfo(/* bitrate= */ 5054232, /* audioGroupId= */ "aud1"),
- createVariantInfo(/* bitrate= */ 5275609, /* audioGroupId= */ "aud2")));
+ createVariantInfo(/* peakBitrate= */ 5054232, /* audioGroupId= */ "aud1"),
+ createVariantInfo(/* peakBitrate= */ 5275609, /* audioGroupId= */ "aud2")));
assertThat(playlist.variants.get(3).format.metadata)
.isEqualTo(
createExtXStreamInfMetadata(
- createVariantInfo(/* bitrate= */ 8399417, /* audioGroupId= */ "aud2"),
- createVariantInfo(/* bitrate= */ 8207417, /* audioGroupId= */ "aud3")));
+ createVariantInfo(/* peakBitrate= */ 8399417, /* audioGroupId= */ "aud2"),
+ createVariantInfo(/* peakBitrate= */ 8207417, /* audioGroupId= */ "aud3")));
assertThat(playlist.audios).hasSize(3);
assertThat(playlist.audios.get(0).format.metadata)
@@ -390,9 +417,10 @@
}
private static HlsTrackMetadataEntry.VariantInfo createVariantInfo(
- long bitrate, String audioGroupId) {
+ int peakBitrate, String audioGroupId) {
return new HlsTrackMetadataEntry.VariantInfo(
- bitrate,
+ /* averageBitrate= */ Format.NO_VALUE,
+ /* peakBitrate= */ peakBitrate,
/* videoGroupId= */ null,
audioGroupId,
/* subtitleGroupId= */ "sub1",
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
index 3fd67b2..ce5ff84 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
@@ -38,7 +38,7 @@
public class HlsMediaPlaylistParserTest {
@Test
- public void testParseMediaPlaylist() throws Exception {
+ public void parseMediaPlaylist() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -149,7 +149,7 @@
}
@Test
- public void testParseSampleAesMethod() throws Exception {
+ public void parseSampleAesMethod() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -178,7 +178,7 @@
}
@Test
- public void testParseSampleAesCencMethod() throws Exception {
+ public void parseSampleAesCencMethod() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -202,7 +202,7 @@
}
@Test
- public void testParseSampleAesCtrMethod() throws Exception {
+ public void parseSampleAesCtrMethod() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -226,7 +226,7 @@
}
@Test
- public void testMultipleExtXKeysForSingleSegment() throws Exception {
+ public void multipleExtXKeysForSingleSegment() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -303,7 +303,7 @@
}
@Test
- public void testGapTag() throws IOException {
+ public void gapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test2.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -338,7 +338,7 @@
}
@Test
- public void testMapTag() throws IOException {
+ public void mapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -368,7 +368,7 @@
}
@Test
- public void testEncryptedMapTag() throws IOException {
+ public void encryptedMapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -399,7 +399,7 @@
}
@Test
- public void testEncryptedMapTagWithNoIvFails() throws IOException {
+ public void encryptedMapTagWithNoIvFails() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -423,7 +423,7 @@
}
@Test
- public void testMasterPlaylistAttributeInheritance() throws IOException {
+ public void masterPlaylistAttributeInheritance() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -466,7 +466,7 @@
}
@Test
- public void testVariableSubstitution() throws IOException {
+ public void variableSubstitution() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/substitution.m3u8");
String playlistString =
"#EXTM3U\n"
@@ -489,7 +489,7 @@
}
@Test
- public void testInheritedVariableSubstitution() throws IOException {
+ public void inheritedVariableSubstitution() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
diff --git a/tree/library/smoothstreaming/build.gradle b/tree/library/smoothstreaming/build.gradle
index 6ced528..404f1d6 100644
--- a/tree/library/smoothstreaming/build.gradle
+++ b/tree/library/smoothstreaming/build.gradle
@@ -33,6 +33,8 @@
}
}
+ sourceSets.test.assets.srcDir '../../testdata/src/test/assets/'
+
testOptions.unitTests.includeAndroidResources = true
}
@@ -40,6 +42,7 @@
implementation project(modulePrefix + 'library-core')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
diff --git a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
index ec4616a..5ce2e6a 100644
--- a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
+++ b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
@@ -38,6 +38,7 @@
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.List;
@@ -80,7 +81,7 @@
private SsManifest manifest;
private int currentManifestChunkOffset;
- private IOException fatalError;
+ @Nullable private IOException fatalError;
/**
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
@@ -106,15 +107,21 @@
for (int i = 0; i < extractorWrappers.length; i++) {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i);
Format format = streamElement.formats[manifestTrackIndex];
+ @Nullable
TrackEncryptionBox[] trackEncryptionBoxes =
- format.drmInitData != null ? manifest.protectionElement.trackEncryptionBoxes : null;
+ format.drmInitData != null
+ ? Assertions.checkNotNull(manifest.protectionElement).trackEncryptionBoxes
+ : null;
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0;
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
- FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
- FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
- | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, null, track, null);
+ FragmentedMp4Extractor extractor =
+ new FragmentedMp4Extractor(
+ FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
+ | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX,
+ /* timestampAdjuster= */ null,
+ track);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, streamElement.type, format);
}
}
@@ -241,7 +248,6 @@
trackSelection.getSelectedFormat(),
dataSource,
uri,
- null,
currentAbsoluteChunkIndex,
chunkStartTimeUs,
chunkEndTimeUs,
@@ -270,15 +276,14 @@
Format format,
DataSource dataSource,
Uri uri,
- String cacheKey,
int chunkIndex,
long chunkStartTimeUs,
long chunkEndTimeUs,
long chunkSeekTimeUs,
int trackSelectionReason,
- Object trackSelectionData,
+ @Nullable Object trackSelectionData,
ChunkExtractorWrapper extractorWrapper) {
- DataSpec dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, cacheKey);
+ DataSpec dataSpec = new DataSpec(uri);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs;
diff --git a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
index f7940fe..8efff23 100644
--- a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
+++ b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
@@ -47,7 +47,7 @@
private final SsChunkSource.Factory chunkSourceFactory;
@Nullable private final TransferListener transferListener;
private final LoaderErrorThrower manifestLoaderErrorThrower;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final EventDispatcher eventDispatcher;
private final Allocator allocator;
@@ -65,7 +65,7 @@
SsChunkSource.Factory chunkSourceFactory,
@Nullable TransferListener transferListener,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
LoaderErrorThrower manifestLoaderErrorThrower,
@@ -259,7 +259,7 @@
}
private static TrackGroupArray buildTrackGroups(
- SsManifest manifest, DrmSessionManager<?> drmSessionManager) {
+ SsManifest manifest, DrmSessionManager drmSessionManager) {
TrackGroup[] trackGroups = new TrackGroup[manifest.streamElements.length];
for (int i = 0; i < manifest.streamElements.length; i++) {
Format[] manifestFormats = manifest.streamElements[i].formats;
diff --git a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
index 6836b5c..ac3085c 100644
--- a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
+++ b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
@@ -21,6 +21,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
@@ -53,6 +54,7 @@
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** A SmoothStreaming {@link MediaSource}. */
@@ -70,11 +72,11 @@
@Nullable private final DataSource.Factory manifestDataSourceFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private DrmSessionManager<?> drmSessionManager;
+ private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
@Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser;
- @Nullable private List<StreamKey> streamKeys;
+ private List<StreamKey> streamKeys;
@Nullable private Object tag;
/**
@@ -105,15 +107,14 @@
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
+ streamKeys = Collections.emptyList();
}
/**
- * Sets a tag for the media source which will be published in the {@link Timeline} of the source
- * as {@link Timeline.Window#tag}.
- *
- * @param tag A tag for the media source.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
@@ -196,7 +197,7 @@
* @return This factory, for convenience.
*/
@Override
- public Factory setDrmSessionManager(@Nullable DrmSessionManager<?> drmSessionManager) {
+ public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager =
drmSessionManager != null
? drmSessionManager
@@ -204,12 +205,26 @@
return this;
}
+ /**
+ * @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
+ * #createMediaSource(MediaItem)} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
- public Factory setStreamKeys(List<StreamKey> streamKeys) {
- this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
+ public Factory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
+ this.streamKeys = streamKeys != null ? streamKeys : Collections.emptyList();
return this;
}
+ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @Override
+ public SsMediaSource createMediaSource(Uri uri) {
+ return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
+ }
+
/**
* Returns a new {@link SsMediaSource} using the current parameters and the specified sideloaded
* manifest.
@@ -220,7 +235,7 @@
*/
public SsMediaSource createMediaSource(SsManifest manifest) {
Assertions.checkArgument(!manifest.isLive);
- if (streamKeys != null) {
+ if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
return new SsMediaSource(
@@ -271,21 +286,27 @@
/**
* Returns a new {@link SsMediaSource} using the current parameters.
*
- * @param manifestUri The manifest {@link Uri}.
+ * @param mediaItem The {@link MediaItem}.
* @return The new {@link SsMediaSource}.
+ * @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
- public SsMediaSource createMediaSource(Uri manifestUri) {
+ public SsMediaSource createMediaSource(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser = this.manifestParser;
if (manifestParser == null) {
manifestParser = new SsManifestParser();
}
- if (streamKeys != null) {
+ List<StreamKey> streamKeys =
+ !mediaItem.playbackProperties.streamKeys.isEmpty()
+ ? mediaItem.playbackProperties.streamKeys
+ : this.streamKeys;
+ if (!streamKeys.isEmpty()) {
manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys);
}
return new SsMediaSource(
/* manifest= */ null,
- Assertions.checkNotNull(manifestUri),
+ mediaItem.playbackProperties.sourceUri,
manifestDataSourceFactory,
manifestParser,
chunkSourceFactory,
@@ -293,7 +314,7 @@
drmSessionManager,
loadErrorHandlingPolicy,
livePresentationDelayMs,
- tag);
+ mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
@Override
@@ -322,7 +343,7 @@
private final DataSource.Factory manifestDataSourceFactory;
private final SsChunkSource.Factory chunkSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private final DrmSessionManager<?> drmSessionManager;
+ private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final long livePresentationDelayMs;
private final EventDispatcher manifestEventDispatcher;
@@ -505,7 +526,7 @@
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser,
SsChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
- DrmSessionManager<?> drmSessionManager,
+ DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long livePresentationDelayMs,
@Nullable Object tag) {
@@ -597,8 +618,8 @@
// Loader.Callback implementation
@Override
- public void onLoadCompleted(ParsingLoadable<SsManifest> loadable, long elapsedRealtimeMs,
- long loadDurationMs) {
+ public void onLoadCompleted(
+ ParsingLoadable<SsManifest> loadable, long elapsedRealtimeMs, long loadDurationMs) {
manifestEventDispatcher.loadCompleted(
loadable.dataSpec,
loadable.getUri(),
@@ -614,8 +635,11 @@
}
@Override
- public void onLoadCanceled(ParsingLoadable<SsManifest> loadable, long elapsedRealtimeMs,
- long loadDurationMs, boolean released) {
+ public void onLoadCanceled(
+ ParsingLoadable<SsManifest> loadable,
+ long elapsedRealtimeMs,
+ long loadDurationMs,
+ boolean released) {
manifestEventDispatcher.loadCanceled(
loadable.dataSpec,
loadable.getUri(),
diff --git a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
index d395e95..d848542 100644
--- a/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
+++ b/tree/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
@@ -23,6 +23,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
@@ -386,7 +387,7 @@
if (type == C.TRACK_TYPE_VIDEO || type == C.TRACK_TYPE_AUDIO) {
Format[] formats = streamElement.formats;
for (int i = 0; i < formats.length; i++) {
- formats[i] = formats[i].copyWithDrmInitData(drmInitData);
+ formats[i] = formats[i].buildUpon().setDrmInitData(drmInitData).build();
}
}
}
@@ -663,96 +664,65 @@
@Override
public void parseStartTag(XmlPullParser parser) throws ParserException {
- int type = (Integer) getNormalizedAttribute(KEY_TYPE);
- String id = parser.getAttributeValue(null, KEY_INDEX);
- String name = (String) getNormalizedAttribute(KEY_NAME);
- int bitrate = parseRequiredInt(parser, KEY_BITRATE);
- String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
+ Format.Builder formatBuilder = new Format.Builder();
+ @Nullable String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
+ int type = (Integer) getNormalizedAttribute(KEY_TYPE);
if (type == C.TRACK_TYPE_VIDEO) {
- int width = parseRequiredInt(parser, KEY_MAX_WIDTH);
- int height = parseRequiredInt(parser, KEY_MAX_HEIGHT);
List<byte[]> codecSpecificData = buildCodecSpecificData(
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
- format =
- Format.createVideoContainerFormat(
- id,
- name,
- MimeTypes.VIDEO_MP4,
- sampleMimeType,
- /* codecs= */ null,
- /* metadata= */ null,
- bitrate,
- width,
- height,
- /* frameRate= */ Format.NO_VALUE,
- codecSpecificData,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0);
+ formatBuilder
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setWidth(parseRequiredInt(parser, KEY_MAX_WIDTH))
+ .setHeight(parseRequiredInt(parser, KEY_MAX_HEIGHT))
+ .setInitializationData(codecSpecificData);
} else if (type == C.TRACK_TYPE_AUDIO) {
- sampleMimeType = sampleMimeType == null ? MimeTypes.AUDIO_AAC : sampleMimeType;
- int channels = parseRequiredInt(parser, KEY_CHANNELS);
- int samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
+ if (sampleMimeType == null) {
+ // If we don't know the MIME type, assume AAC.
+ sampleMimeType = MimeTypes.AUDIO_AAC;
+ }
+ int channelCount = parseRequiredInt(parser, KEY_CHANNELS);
+ int sampleRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
List<byte[]> codecSpecificData = buildCodecSpecificData(
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
if (codecSpecificData.isEmpty() && MimeTypes.AUDIO_AAC.equals(sampleMimeType)) {
- codecSpecificData = Collections.singletonList(
- CodecSpecificDataUtil.buildAacLcAudioSpecificConfig(samplingRate, channels));
+ codecSpecificData =
+ Collections.singletonList(
+ AacUtil.buildAacLcAudioSpecificConfig(sampleRate, channelCount));
}
- String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
- format =
- Format.createAudioContainerFormat(
- id,
- name,
- MimeTypes.AUDIO_MP4,
- sampleMimeType,
- /* codecs= */ null,
- /* metadata= */ null,
- bitrate,
- channels,
- samplingRate,
- codecSpecificData,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- language);
+ formatBuilder
+ .setContainerMimeType(MimeTypes.AUDIO_MP4)
+ .setChannelCount(channelCount)
+ .setSampleRate(sampleRate)
+ .setInitializationData(codecSpecificData);
} else if (type == C.TRACK_TYPE_TEXT) {
- String subType = (String) getNormalizedAttribute(KEY_SUB_TYPE);
@C.RoleFlags int roleFlags = 0;
- switch (subType) {
- case "CAPT":
- roleFlags = C.ROLE_FLAG_CAPTION;
- break;
- case "DESC":
- roleFlags = C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND;
- break;
- default:
- break;
+ @Nullable String subType = (String) getNormalizedAttribute(KEY_SUB_TYPE);
+ if (subType != null) {
+ switch (subType) {
+ case "CAPT":
+ roleFlags = C.ROLE_FLAG_CAPTION;
+ break;
+ case "DESC":
+ roleFlags = C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND;
+ break;
+ default:
+ break;
+ }
}
- String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
- format =
- Format.createTextContainerFormat(
- id,
- name,
- MimeTypes.APPLICATION_MP4,
- sampleMimeType,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- roleFlags,
- language);
+ formatBuilder.setContainerMimeType(MimeTypes.APPLICATION_MP4).setRoleFlags(roleFlags);
} else {
- format =
- Format.createContainerFormat(
- id,
- name,
- MimeTypes.APPLICATION_MP4,
- sampleMimeType,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ formatBuilder.setContainerMimeType(MimeTypes.APPLICATION_MP4);
}
+
+ format =
+ formatBuilder
+ .setId(parser.getAttributeValue(null, KEY_INDEX))
+ .setLabel((String) getNormalizedAttribute(KEY_NAME))
+ .setSampleMimeType(sampleMimeType)
+ .setAverageBitrate(parseRequiredInt(parser, KEY_BITRATE))
+ .setLanguage((String) getNormalizedAttribute(KEY_LANGUAGE))
+ .build();
}
@Override
@@ -764,7 +734,7 @@
ArrayList<byte[]> csd = new ArrayList<>();
if (!TextUtils.isEmpty(codecSpecificDataString)) {
byte[] codecPrivateData = Util.getBytesFromHexString(codecSpecificDataString);
- byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData);
+ @Nullable byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData);
if (split == null) {
csd.add(codecPrivateData);
} else {
@@ -774,6 +744,7 @@
return csd;
}
+ @Nullable
private static String fourCCToMimeType(String fourCC) {
if (fourCC.equalsIgnoreCase("H264") || fourCC.equalsIgnoreCase("X264")
|| fourCC.equalsIgnoreCase("AVC1") || fourCC.equalsIgnoreCase("DAVC")) {
diff --git a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultMediaSourceFactoryTest.java b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultMediaSourceFactoryTest.java
new file mode 100644
index 0000000..54c2923
--- /dev/null
+++ b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultMediaSourceFactoryTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source.smoothstreaming;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.util.MimeTypes;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit test for creating SmoothStreaming media sources with the {@link DefaultMediaSourceFactory}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DefaultMediaSourceFactoryTest {
+
+ private static final String URI_MEDIA = "http://exoplayer.dev/video";
+
+ @Test
+ public void createMediaSource_withMimeType_smoothstreamingSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_SS)
+ .build();
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+ assertThat(mediaSource).isInstanceOf(SsMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withTag_tagInSource() {
+ Object tag = new Object();
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setSourceUri(URI_MEDIA)
+ .setMimeType(MimeTypes.APPLICATION_SS)
+ .setTag(tag)
+ .build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource.getTag()).isEqualTo(tag);
+ }
+
+ @Test
+ public void createMediaSource_withIsmPath_smoothstreamingSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.ism").build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(SsMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withManifestPath_smoothstreamingSource() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + ".ism/Manifest").build();
+
+ MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isInstanceOf(SsMediaSource.class);
+ }
+
+ @Test
+ public void createMediaSource_withNull_usesNonNullDefaults() {
+ DefaultMediaSourceFactory defaultMediaSourceFactory =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
+ MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_MEDIA + "/file.ism").build();
+
+ MediaSource mediaSource =
+ defaultMediaSourceFactory
+ .setDrmSessionManager(null)
+ .setDrmHttpDataSourceFactory(null)
+ .setLoadErrorHandlingPolicy(null)
+ .createMediaSource(mediaItem);
+
+ assertThat(mediaSource).isNotNull();
+ }
+
+ @Test
+ public void getSupportedTypes_smoothstreamingModule_containsTypeSS() {
+ int[] supportedTypes =
+ DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext())
+ .getSupportedTypes();
+
+ assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER, C.TYPE_SS);
+ }
+}
diff --git a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriodTest.java b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriodTest.java
index b9c63f8..a20e579 100644
--- a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriodTest.java
+++ b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriodTest.java
@@ -82,41 +82,26 @@
}
private static Format createVideoFormat(int bitrate) {
- return Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.VIDEO_MP4,
- MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(bitrate)
+ .build();
}
private static Format createAudioFormat(int bitrate) {
- return Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.AUDIO_MP4,
- MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- bitrate,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.AUDIO_MP4)
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setAverageBitrate(bitrate)
+ .build();
}
private static Format createTextFormat(String language) {
- return Format.createContainerFormat(
- /* id= */ null,
- /* label= */ null,
- MimeTypes.APPLICATION_MP4,
- MimeTypes.TEXT_VTT,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- language);
+ return new Format.Builder()
+ .setContainerMimeType(MimeTypes.APPLICATION_MP4)
+ .setSampleMimeType(MimeTypes.TEXT_VTT)
+ .setLanguage(language)
+ .build();
}
}
diff --git a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java
index 94be71e..60d9c40 100644
--- a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java
+++ b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java
@@ -27,12 +27,12 @@
@RunWith(AndroidJUnit4.class)
public final class SsManifestParserTest {
- private static final String SAMPLE_ISMC_1 = "sample_ismc_1";
- private static final String SAMPLE_ISMC_2 = "sample_ismc_2";
+ private static final String SAMPLE_ISMC_1 = "smooth-streaming/sample_ismc_1";
+ private static final String SAMPLE_ISMC_2 = "smooth-streaming/sample_ismc_2";
/** Simple test to ensure the sample manifests parse without any exceptions being thrown. */
@Test
- public void testParseSmoothStreamingManifest() throws IOException {
+ public void parseSmoothStreamingManifest() throws IOException {
SsManifestParser parser = new SsManifestParser();
parser.parse(
Uri.parse("https://example.com/test.ismc"),
diff --git a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
index 427cd9b..5715a78 100644
--- a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
+++ b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
@@ -37,7 +37,7 @@
public class SsManifestTest {
@Test
- public void testCopy() throws Exception {
+ public void copy() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest =
createSsManifest(
@@ -58,7 +58,7 @@
}
@Test
- public void testCopyRemoveStreamElement() throws Exception {
+ public void copyRemoveStreamElement() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest =
createSsManifest(
@@ -114,15 +114,10 @@
}
private static Format newFormat(String id) {
- return Format.createContainerFormat(
- id,
- /* label= */ null,
- MimeTypes.VIDEO_MP4,
- MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- /* bitrate= */ Format.NO_VALUE,
- /* selectionFlags= */ 0,
- /* roleFlags= */ 0,
- /* language= */ null);
+ return new Format.Builder()
+ .setId(id)
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .build();
}
}
diff --git a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/DownloadHelperTest.java b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/DownloadHelperTest.java
index a103f89..b6d29d8 100644
--- a/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/DownloadHelperTest.java
+++ b/tree/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/DownloadHelperTest.java
@@ -34,11 +34,11 @@
ApplicationProvider.getApplicationContext(),
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0]);
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0]);
DownloadHelper.forSmoothStreaming(
Uri.parse("http://uri"),
new FakeDataSource.Factory(),
- (handler, videoListener, audioListener, text, metadata, drm) -> new Renderer[0],
+ (handler, videoListener, audioListener, text, metadata) -> new Renderer[0],
/* drmSessionManager= */ null,
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
}
diff --git a/tree/library/ui/build.gradle b/tree/library/ui/build.gradle
index b6bf139..f404ee3 100644
--- a/tree/library/ui/build.gradle
+++ b/tree/library/ui/build.gradle
@@ -41,6 +41,7 @@
api 'androidx.media:media:' + androidxMediaVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
index 0841296..39ea45a 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
@@ -79,7 +79,13 @@
// Player.EventListener implementation.
@Override
- public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public final void onPlaybackStateChanged(@Player.State int playbackState) {
+ updateAndPost();
+ }
+
+ @Override
+ public final void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int playbackState) {
updateAndPost();
}
@@ -151,6 +157,10 @@
+ format.height
+ getPixelAspectRatioString(format.pixelWidthHeightRatio)
+ getDecoderCountersBufferCountString(decoderCounters)
+ + " vfpo: "
+ + getVideoFrameProcessingOffsetAverageString(
+ decoderCounters.totalVideoFrameProcessingOffsetUs,
+ decoderCounters.videoFrameProcessingOffsetCount)
+ ")";
}
@@ -191,4 +201,13 @@
: (" par:" + String.format(Locale.US, "%.02f", pixelAspectRatio));
}
+ private static String getVideoFrameProcessingOffsetAverageString(
+ long totalOffsetUs, int frameCount) {
+ if (frameCount == 0) {
+ return "N/A";
+ } else {
+ long averageUs = (long) ((double) totalOffsetUs / frameCount);
+ return String.valueOf(averageUs);
+ }
+ }
}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
index 2da203a..b7e96d2 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java
@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer2.ui;
-import android.annotation.TargetApi;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -55,8 +55,6 @@
*
* The following attributes can be set on a DefaultTimeBar when used in a layout XML file:
*
- * <p>
- *
* <ul>
* <li><b>{@code bar_height}</b> - Dimension for the height of the time bar.
* <ul>
@@ -166,6 +164,9 @@
private static final int DEFAULT_INCREMENT_COUNT = 20;
+ private static final float SHOWN_SCRUBBER_SCALE = 1.0f;
+ private static final float HIDDEN_SCRUBBER_SCALE = 0.0f;
+
/**
* The name of the Android SDK view that most closely resembles this custom view. Used as the
* class name for accessibility.
@@ -204,6 +205,8 @@
private int lastCoarseScrubXPosition;
private @MonotonicNonNull Rect lastExclusionRectangle;
+ private ValueAnimator scrubberScalingAnimator;
+ private float scrubberScale;
private boolean scrubbing;
private long scrubPosition;
private long duration;
@@ -327,6 +330,13 @@
(Math.max(scrubberDisabledSize, Math.max(scrubberEnabledSize, scrubberDraggedSize)) + 1)
/ 2;
}
+ scrubberScale = 1.0f;
+ scrubberScalingAnimator = new ValueAnimator();
+ scrubberScalingAnimator.addUpdateListener(
+ animation -> {
+ scrubberScale = (float) animation.getAnimatedValue();
+ invalidate(seekBounds);
+ });
duration = C.TIME_UNSET;
keyTimeIncrement = C.TIME_UNSET;
keyCountIncrement = DEFAULT_INCREMENT_COUNT;
@@ -336,6 +346,44 @@
}
}
+ /** Shows the scrubber handle. */
+ public void showScrubber() {
+ showScrubber(/* showAnimationDurationMs= */ 0);
+ }
+
+ /**
+ * Shows the scrubber handle with animation.
+ *
+ * @param showAnimationDurationMs The duration for scrubber showing animation.
+ */
+ public void showScrubber(long showAnimationDurationMs) {
+ if (scrubberScalingAnimator.isStarted()) {
+ scrubberScalingAnimator.cancel();
+ }
+ scrubberScalingAnimator.setFloatValues(scrubberScale, SHOWN_SCRUBBER_SCALE);
+ scrubberScalingAnimator.setDuration(showAnimationDurationMs);
+ scrubberScalingAnimator.start();
+ }
+
+ /** Hides the scrubber handle. */
+ public void hideScrubber() {
+ hideScrubber(/* hideAnimationDurationMs= */ 0);
+ }
+
+ /**
+ * Hides the scrubber handle with animation.
+ *
+ * @param hideAnimationDurationMs The duration for scrubber hiding animation.
+ */
+ public void hideScrubber(long hideAnimationDurationMs) {
+ if (scrubberScalingAnimator.isStarted()) {
+ scrubberScalingAnimator.cancel();
+ }
+ scrubberScalingAnimator.setFloatValues(scrubberScale, HIDDEN_SCRUBBER_SCALE);
+ scrubberScalingAnimator.setDuration(hideAnimationDurationMs);
+ scrubberScalingAnimator.start();
+ }
+
/**
* Sets the color for the portion of the time bar representing media before the playback position.
*
@@ -626,7 +674,6 @@
event.setClassName(ACCESSIBILITY_CLASS_NAME);
}
- @TargetApi(21)
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
@@ -816,11 +863,11 @@
if (scrubberDrawable == null) {
int scrubberSize = (scrubbing || isFocused()) ? scrubberDraggedSize
: (isEnabled() ? scrubberEnabledSize : scrubberDisabledSize);
- int playheadRadius = scrubberSize / 2;
+ int playheadRadius = (int) ((scrubberSize * scrubberScale) / 2);
canvas.drawCircle(playheadX, playheadY, playheadRadius, scrubberPaint);
} else {
- int scrubberDrawableWidth = scrubberDrawable.getIntrinsicWidth();
- int scrubberDrawableHeight = scrubberDrawable.getIntrinsicHeight();
+ int scrubberDrawableWidth = (int) (scrubberDrawable.getIntrinsicWidth() * scrubberScale);
+ int scrubberDrawableHeight = (int) (scrubberDrawable.getIntrinsicHeight() * scrubberScale);
scrubberDrawable.setBounds(
playheadX - scrubberDrawableWidth / 2,
playheadY - scrubberDrawableHeight / 2,
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
index bfb4e01..778f033 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
@@ -33,6 +33,8 @@
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ControlDispatcher;
+import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
@@ -67,13 +69,13 @@
* <li><b>{@code rewind_increment}</b> - The duration of the rewind applied when the user taps the
* rewind button, in milliseconds. Use zero to disable the rewind button.
* <ul>
- * <li>Corresponding method: {@link #setRewindIncrementMs(int)}
- * <li>Default: {@link #DEFAULT_REWIND_MS}
+ * <li>Corresponding method: {@link #setControlDispatcher(ControlDispatcher)}
+ * <li>Default: {@link DefaultControlDispatcher#DEFAULT_REWIND_MS}
* </ul>
* <li><b>{@code fastforward_increment}</b> - Like {@code rewind_increment}, but for fast forward.
* <ul>
- * <li>Corresponding method: {@link #setFastForwardIncrementMs(int)}
- * <li>Default: {@link #DEFAULT_FAST_FORWARD_MS}
+ * <li>Corresponding method: {@link #setControlDispatcher(ControlDispatcher)}
+ * <li>Default: {@link DefaultControlDispatcher#DEFAULT_FAST_FORWARD_MS}
* </ul>
* <li><b>{@code repeat_toggle_modes}</b> - A flagged enumeration value specifying which repeat
* mode toggle options are enabled. Valid values are: {@code none}, {@code one}, {@code all},
@@ -136,8 +138,6 @@
* ExoPlayer library, and will be inflated for use by PlayerControlView. The view identifies and
* binds its children by looking for the following ids:
*
- * <p>
- *
* <ul>
* <li><b>{@code exo_play}</b> - The play button.
* <ul>
@@ -246,10 +246,6 @@
void onProgressUpdate(long position, long bufferedPosition);
}
- /** The default fast forward increment, in milliseconds. */
- public static final int DEFAULT_FAST_FORWARD_MS = 15000;
- /** The default rewind increment, in milliseconds. */
- public static final int DEFAULT_REWIND_MS = 5000;
/** The default show timeout, in milliseconds. */
public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000;
/** The default repeat toggle modes. */
@@ -260,7 +256,6 @@
/** The maximum number of windows that can be shown in a multi-window time bar. */
public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100;
- private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
/** The maximum interval between time bar position updates. */
private static final int MAX_UPDATE_INTERVAL_MS = 1000;
@@ -307,8 +302,6 @@
private boolean showMultiWindowTimeBar;
private boolean multiWindowTimeBar;
private boolean scrubbing;
- private int rewindMs;
- private int fastForwardMs;
private int showTimeoutMs;
private int timeBarMinUpdateIntervalMs;
private @RepeatModeUtil.RepeatToggleModes int repeatToggleModes;
@@ -344,13 +337,13 @@
@Nullable AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_player_control_view;
- rewindMs = DEFAULT_REWIND_MS;
- fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS;
hideAtMs = C.TIME_UNSET;
showShuffleButton = false;
+ int rewindMs = DefaultControlDispatcher.DEFAULT_REWIND_MS;
+ int fastForwardMs = DefaultControlDispatcher.DEFAULT_FAST_FORWARD_MS;
if (playbackAttrs != null) {
TypedArray a =
context
@@ -384,7 +377,8 @@
extraAdGroupTimesMs = new long[0];
extraPlayedAdGroups = new boolean[0];
componentListener = new ComponentListener();
- controlDispatcher = new com.google.android.exoplayer2.DefaultControlDispatcher();
+ controlDispatcher =
+ new com.google.android.exoplayer2.DefaultControlDispatcher(fastForwardMs, rewindMs);
updateProgressAction = this::updateProgress;
hideAction = this::hide;
@@ -589,37 +583,39 @@
/**
* Sets the {@link com.google.android.exoplayer2.ControlDispatcher}.
*
- * @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}, or null
- * to use {@link com.google.android.exoplayer2.DefaultControlDispatcher}.
+ * @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}.
*/
- public void setControlDispatcher(
- @Nullable com.google.android.exoplayer2.ControlDispatcher controlDispatcher) {
- this.controlDispatcher =
- controlDispatcher == null
- ? new com.google.android.exoplayer2.DefaultControlDispatcher()
- : controlDispatcher;
+ public void setControlDispatcher(ControlDispatcher controlDispatcher) {
+ if (this.controlDispatcher != controlDispatcher) {
+ this.controlDispatcher = controlDispatcher;
+ updateNavigation();
+ }
}
/**
- * Sets the rewind increment in milliseconds.
- *
- * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
- * rewind button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setRewindIncrementMs(int rewindMs) {
- this.rewindMs = rewindMs;
- updateNavigation();
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setRewindIncrementMs(rewindMs);
+ updateNavigation();
+ }
}
/**
- * Sets the fast forward increment in milliseconds.
- *
- * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
- * cause the fast forward button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setFastForwardIncrementMs(int fastForwardMs) {
- this.fastForwardMs = fastForwardMs;
- updateNavigation();
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setFastForwardIncrementMs(fastForwardMs);
+ updateNavigation();
+ }
}
/**
@@ -830,8 +826,8 @@
boolean isSeekable = window.isSeekable;
enableSeeking = isSeekable;
enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious();
- enableRewind = isSeekable && rewindMs > 0;
- enableFastForward = isSeekable && fastForwardMs > 0;
+ enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
+ enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = window.isDynamic || player.hasNext();
}
}
@@ -1012,7 +1008,7 @@
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
// Calculate the delay until the next update in real time, taking playbackSpeed into account.
- float playbackSpeed = player.getPlaybackParameters().speed;
+ float playbackSpeed = player.getPlaybackSpeed();
long delayMs =
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
@@ -1042,59 +1038,6 @@
view.setVisibility(VISIBLE);
}
- private void previous(Player player) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- timeline.getWindow(windowIndex, window);
- int previousWindowIndex = player.getPreviousWindowIndex();
- if (previousWindowIndex != C.INDEX_UNSET
- && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
- || (window.isDynamic && !window.isSeekable))) {
- seekTo(player, previousWindowIndex, C.TIME_UNSET);
- } else {
- seekTo(player, windowIndex, /* positionMs= */ 0);
- }
- }
-
- private void next(Player player) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- int nextWindowIndex = player.getNextWindowIndex();
- if (nextWindowIndex != C.INDEX_UNSET) {
- seekTo(player, nextWindowIndex, C.TIME_UNSET);
- } else if (timeline.getWindow(windowIndex, window).isDynamic) {
- seekTo(player, windowIndex, C.TIME_UNSET);
- }
- }
-
- private void rewind(Player player) {
- if (player.isCurrentWindowSeekable() && rewindMs > 0) {
- seekToOffset(player, -rewindMs);
- }
- }
-
- private void fastForward(Player player) {
- if (player.isCurrentWindowSeekable() && fastForwardMs > 0) {
- seekToOffset(player, fastForwardMs);
- }
- }
-
- private void seekToOffset(Player player, long offsetMs) {
- long positionMs = player.getCurrentPosition() + offsetMs;
- long durationMs = player.getDuration();
- if (durationMs != C.TIME_UNSET) {
- positionMs = Math.min(positionMs, durationMs);
- }
- positionMs = Math.max(positionMs, 0);
- seekTo(player, player.getCurrentWindowIndex(), positionMs);
- }
-
private void seekToTimeBarPosition(Player player, long positionMs) {
int windowIndex;
Timeline timeline = player.getCurrentTimeline();
@@ -1183,9 +1126,9 @@
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
- fastForward(player);
+ controlDispatcher.dispatchFastForward(player);
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) {
- rewind(player);
+ controlDispatcher.dispatchRewind(player);
} else if (event.getRepeatCount() == 0) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -1198,10 +1141,10 @@
controlDispatcher.dispatchSetPlayWhenReady(player, false);
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
- next(player);
+ controlDispatcher.dispatchNext(player);
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- previous(player);
+ controlDispatcher.dispatchPrevious(player);
break;
default:
break;
@@ -1276,7 +1219,14 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
+ updatePlayPauseButton();
+ updateProgress();
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
updatePlayPauseButton();
updateProgress();
}
@@ -1317,13 +1267,13 @@
return;
}
if (nextButton == view) {
- next(player);
+ controlDispatcher.dispatchNext(player);
} else if (previousButton == view) {
- previous(player);
+ controlDispatcher.dispatchPrevious(player);
} else if (fastForwardButton == view) {
- fastForward(player);
+ controlDispatcher.dispatchFastForward(player);
} else if (rewindButton == view) {
- rewind(player);
+ controlDispatcher.dispatchRewind(player);
} else if (playButton == view) {
if (player.getPlaybackState() == Player.STATE_IDLE) {
if (playbackPreparer != null) {
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
index 9f0c828..b3d646c 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
@@ -37,7 +37,6 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
@@ -94,14 +93,14 @@
* <li><b>{@code rewindIncrementMs}</b> - Sets the rewind increment. If set to zero the rewind
* action is not displayed.
* <ul>
- * <li>Corresponding setter: {@link #setRewindIncrementMs(long)}
- * <li>Default: {@link #DEFAULT_REWIND_MS} (5000)
+ * <li>Corresponding setter: {@link #setControlDispatcher(ControlDispatcher)}
+ * <li>Default: {@link DefaultControlDispatcher#DEFAULT_REWIND_MS} (5000)
* </ul>
* <li><b>{@code fastForwardIncrementMs}</b> - Sets the fast forward increment. If set to zero the
* fast forward action is not displayed.
* <ul>
- * <li>Corresponding setter: {@link #setFastForwardIncrementMs(long)}
- * <li>Default: {@link #DEFAULT_FAST_FORWARD_MS} (15000)
+ * <li>Corresponding setter: {@link #setControlDispatcher(ControlDispatcher)}
+ * <li>Default: {@link DefaultControlDispatcher#DEFAULT_FAST_FORWARD_MS} (15000)
* </ul>
* </ul>
*
@@ -140,7 +139,7 @@
*
* @param player The {@link Player} for which a notification is being built.
*/
- String getCurrentContentTitle(Player player);
+ CharSequence getCurrentContentTitle(Player player);
/**
* Creates a content intent for the current media item.
@@ -160,7 +159,7 @@
* @param player The {@link Player} for which a notification is being built.
*/
@Nullable
- String getCurrentContentText(Player player);
+ CharSequence getCurrentContentText(Player player);
/**
* Gets the content sub text for the current media item.
@@ -170,7 +169,7 @@
* @param player The {@link Player} for which a notification is being built.
*/
@Nullable
- default String getCurrentSubText(Player player) {
+ default CharSequence getCurrentSubText(Player player) {
return null;
}
@@ -354,13 +353,6 @@
})
public @interface Priority {}
- /** The default fast forward increment, in milliseconds. */
- public static final int DEFAULT_FAST_FORWARD_MS = 15000;
- /** The default rewind increment, in milliseconds. */
- public static final int DEFAULT_REWIND_MS = 5000;
-
- private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
-
private static int instanceIdCounter;
private final Context context;
@@ -392,8 +384,6 @@
private boolean useNavigationActionsInCompactView;
private boolean usePlayPauseActions;
private boolean useStopAction;
- private long fastForwardMs;
- private long rewindMs;
private int badgeIconType;
private boolean colorized;
private int defaults;
@@ -634,8 +624,6 @@
smallIconResourceId = R.drawable.exo_notification_small_icon;
defaults = 0;
priority = NotificationCompat.PRIORITY_LOW;
- fastForwardMs = DEFAULT_FAST_FORWARD_MS;
- rewindMs = DEFAULT_REWIND_MS;
badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
visibility = NotificationCompat.VISIBILITY_PUBLIC;
@@ -701,12 +689,13 @@
/**
* Sets the {@link ControlDispatcher}.
*
- * @param controlDispatcher The {@link ControlDispatcher}, or null to use {@link
- * DefaultControlDispatcher}.
+ * @param controlDispatcher The {@link ControlDispatcher}.
*/
public final void setControlDispatcher(ControlDispatcher controlDispatcher) {
- this.controlDispatcher =
- controlDispatcher != null ? controlDispatcher : new DefaultControlDispatcher();
+ if (this.controlDispatcher != controlDispatcher) {
+ this.controlDispatcher = controlDispatcher;
+ invalidate();
+ }
}
/**
@@ -725,31 +714,29 @@
}
/**
- * Sets the fast forward increment in milliseconds.
- *
- * @param fastForwardMs The fast forward increment in milliseconds. A value of zero will cause the
- * fast forward action to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public final void setFastForwardIncrementMs(long fastForwardMs) {
- if (this.fastForwardMs == fastForwardMs) {
- return;
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setFastForwardIncrementMs(fastForwardMs);
+ invalidate();
}
- this.fastForwardMs = fastForwardMs;
- invalidate();
}
/**
- * Sets the rewind increment in milliseconds.
- *
- * @param rewindMs The rewind increment in milliseconds. A value of zero will cause the rewind
- * action to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public final void setRewindIncrementMs(long rewindMs) {
- if (this.rewindMs == rewindMs) {
- return;
+ if (controlDispatcher instanceof DefaultControlDispatcher) {
+ ((DefaultControlDispatcher) controlDispatcher).setRewindIncrementMs(rewindMs);
+ invalidate();
}
- this.rewindMs = rewindMs;
- invalidate();
}
/**
@@ -1047,6 +1034,7 @@
List<NotificationCompat.Action> actions = new ArrayList<>(actionNames.size());
for (int i = 0; i < actionNames.size(); i++) {
String actionName = actionNames.get(i);
+ @Nullable
NotificationCompat.Action action =
playbackActions.containsKey(actionName)
? playbackActions.get(actionName)
@@ -1146,8 +1134,8 @@
if (!timeline.isEmpty() && !player.isPlayingAd()) {
timeline.getWindow(player.getCurrentWindowIndex(), window);
enablePrevious = window.isSeekable || !window.isDynamic || player.hasPrevious();
- enableRewind = rewindMs > 0;
- enableFastForward = fastForwardMs > 0;
+ enableRewind = controlDispatcher.isRewindEnabled();
+ enableFastForward = controlDispatcher.isFastForwardEnabled();
enableNext = window.isDynamic || player.hasNext();
}
@@ -1222,63 +1210,6 @@
&& player.getPlayWhenReady();
}
- private void previous(Player player) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- timeline.getWindow(windowIndex, window);
- int previousWindowIndex = player.getPreviousWindowIndex();
- if (previousWindowIndex != C.INDEX_UNSET
- && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
- || (window.isDynamic && !window.isSeekable))) {
- seekTo(player, previousWindowIndex, C.TIME_UNSET);
- } else {
- seekTo(player, windowIndex, /* positionMs= */ 0);
- }
- }
-
- private void next(Player player) {
- Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty() || player.isPlayingAd()) {
- return;
- }
- int windowIndex = player.getCurrentWindowIndex();
- int nextWindowIndex = player.getNextWindowIndex();
- if (nextWindowIndex != C.INDEX_UNSET) {
- seekTo(player, nextWindowIndex, C.TIME_UNSET);
- } else if (timeline.getWindow(windowIndex, window).isDynamic) {
- seekTo(player, windowIndex, C.TIME_UNSET);
- }
- }
-
- private void rewind(Player player) {
- if (player.isCurrentWindowSeekable() && rewindMs > 0) {
- seekToOffset(player, /* offsetMs= */ -rewindMs);
- }
- }
-
- private void fastForward(Player player) {
- if (player.isCurrentWindowSeekable() && fastForwardMs > 0) {
- seekToOffset(player, /* offsetMs= */ fastForwardMs);
- }
- }
-
- private void seekToOffset(Player player, long offsetMs) {
- long positionMs = player.getCurrentPosition() + offsetMs;
- long durationMs = player.getDuration();
- if (durationMs != C.TIME_UNSET) {
- positionMs = Math.min(positionMs, durationMs);
- }
- positionMs = Math.max(positionMs, 0);
- seekTo(player, player.getCurrentWindowIndex(), positionMs);
- }
-
- private void seekTo(Player player, int windowIndex, long positionMs) {
- controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs);
- }
-
private boolean shouldShowPauseButton(Player player) {
return player.getPlaybackState() != Player.STATE_ENDED
&& player.getPlaybackState() != Player.STATE_IDLE
@@ -1380,7 +1311,13 @@
private class PlayerListener implements Player.EventListener {
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
+ postStartOrUpdateNotification();
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
postStartOrUpdateNotification();
}
@@ -1395,7 +1332,7 @@
}
@Override
- public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ public void onPlaybackSpeedChanged(float playbackSpeed) {
postStartOrUpdateNotification();
}
@@ -1432,19 +1369,19 @@
playbackPreparer.preparePlayback();
}
} else if (player.getPlaybackState() == Player.STATE_ENDED) {
- seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET);
+ controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET);
}
controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ true);
} else if (ACTION_PAUSE.equals(action)) {
controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ false);
} else if (ACTION_PREVIOUS.equals(action)) {
- previous(player);
+ controlDispatcher.dispatchPrevious(player);
} else if (ACTION_REWIND.equals(action)) {
- rewind(player);
+ controlDispatcher.dispatchRewind(player);
} else if (ACTION_FAST_FORWARD.equals(action)) {
- fastForward(player);
+ controlDispatcher.dispatchFastForward(player);
} else if (ACTION_NEXT.equals(action)) {
- next(player);
+ controlDispatcher.dispatchNext(player);
} else if (ACTION_STOP.equals(action)) {
controlDispatcher.dispatchStop(player, /* reset= */ true);
} else if (ACTION_DISMISS.equals(action)) {
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
index 0316864..60b7278 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.ui;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -40,6 +39,7 @@
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
@@ -143,6 +143,12 @@
* <li>Corresponding method: None
* <li>Default: {@code surface_view}
* </ul>
+ * <li><b>{@code use_sensor_rotation}</b> - Whether to use the orientation sensor for rotation
+ * during spherical playbacks (if available).
+ * <ul>
+ * <li>Corresponding method: {@link #setUseSensorRotation(boolean)}
+ * <li>Default: {@code true}
+ * </ul>
* <li><b>{@code shutter_background_color}</b> - The background color of the {@code exo_shutter}
* view.
* <ul>
@@ -187,8 +193,6 @@
* inflated for use by PlayerView. The view identifies and binds its children by looking for the
* following ids:
*
- * <p>
- *
* <ul>
* <li><b>{@code exo_content_frame}</b> - A frame whose aspect ratio is resized based on the video
* or album art of the media being played, and the configured {@code resize_mode}. The video
@@ -308,6 +312,7 @@
@Nullable private Drawable defaultArtwork;
private @ShowBuffering int showBuffering;
private boolean keepContentOnPlayerReset;
+ private boolean useSensorRotation;
@Nullable private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
@Nullable private CharSequence customErrorMessage;
private int controllerShowTimeoutMs;
@@ -367,6 +372,7 @@
boolean controllerAutoShow = true;
boolean controllerHideDuringAds = true;
int showBuffering = SHOW_BUFFERING_NEVER;
+ useSensorRotation = true;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PlayerView, 0, 0);
try {
@@ -390,6 +396,8 @@
R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset);
controllerHideDuringAds =
a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds);
+ useSensorRotation =
+ a.getBoolean(R.styleable.PlayerView_use_sensor_rotation, useSensorRotation);
} finally {
a.recycle();
}
@@ -422,6 +430,7 @@
case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW:
SphericalGLSurfaceView sphericalGLSurfaceView = new SphericalGLSurfaceView(context);
sphericalGLSurfaceView.setSingleTapListener(componentListener);
+ sphericalGLSurfaceView.setUseSensorRotation(useSensorRotation);
surfaceView = sphericalGLSurfaceView;
break;
case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW:
@@ -747,6 +756,22 @@
}
/**
+ * Sets whether to use the orientation sensor for rotation during spherical playbacks (if
+ * available)
+ *
+ * @param useSensorRotation Whether to use the orientation sensor for rotation during spherical
+ * playbacks.
+ */
+ public void setUseSensorRotation(boolean useSensorRotation) {
+ if (this.useSensorRotation != useSensorRotation) {
+ this.useSensorRotation = useSensorRotation;
+ if (surfaceView instanceof SphericalGLSurfaceView) {
+ ((SphericalGLSurfaceView) surfaceView).setUseSensorRotation(useSensorRotation);
+ }
+ }
+ }
+
+ /**
* Sets whether a buffering spinner is displayed when the player is in the buffering state. The
* buffering spinner is not displayed by default.
*
@@ -965,31 +990,30 @@
/**
* Sets the {@link ControlDispatcher}.
*
- * @param controlDispatcher The {@link ControlDispatcher}, or null to use {@link
- * DefaultControlDispatcher}.
+ * @param controlDispatcher The {@link ControlDispatcher}.
*/
- public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) {
+ public void setControlDispatcher(ControlDispatcher controlDispatcher) {
Assertions.checkStateNotNull(controller);
controller.setControlDispatcher(controlDispatcher);
}
/**
- * Sets the rewind increment in milliseconds.
- *
- * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
- * rewind button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setRewindIncrementMs(int rewindMs) {
Assertions.checkStateNotNull(controller);
controller.setRewindIncrementMs(rewindMs);
}
/**
- * Sets the fast forward increment in milliseconds.
- *
- * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
- * cause the fast forward button to be disabled.
+ * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} with {@link
+ * DefaultControlDispatcher#DefaultControlDispatcher(long, long)}.
*/
+ @SuppressWarnings("deprecation")
+ @Deprecated
public void setFastForwardIncrementMs(int fastForwardMs) {
Assertions.checkStateNotNull(controller);
controller.setFastForwardIncrementMs(fastForwardMs);
@@ -1394,7 +1418,7 @@
errorMessageView.setVisibility(View.VISIBLE);
return;
}
- @Nullable ExoPlaybackException error = player != null ? player.getPlaybackError() : null;
+ @Nullable ExoPlaybackException error = player != null ? player.getPlayerError() : null;
if (error != null && errorMessageProvider != null) {
CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second;
errorMessageView.setText(errorMessage);
@@ -1419,7 +1443,15 @@
}
}
- @TargetApi(23)
+ private void updateControllerVisibility() {
+ if (isPlayingAd() && controllerHideDuringAds) {
+ hideController();
+ } else {
+ maybeShowController(false);
+ }
+ }
+
+ @RequiresApi(23)
private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));
logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null));
@@ -1533,14 +1565,17 @@
// Player.EventListener implementation
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
updateBuffering();
updateErrorMessage();
- if (isPlayingAd() && controllerHideDuringAds) {
- hideController();
- } else {
- maybeShowController(false);
- }
+ updateControllerVisibility();
+ }
+
+ @Override
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
+ updateBuffering();
+ updateControllerVisibility();
}
@Override
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java
new file mode 100644
index 0000000..8d0760e
--- /dev/null
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.ui;
+
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.SparseArray;
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
+import com.google.android.exoplayer2.text.span.RubySpan;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to convert from <a
+ * href="https://developer.android.com/guide/topics/text/spans">span-styled text</a> to HTML.
+ *
+ * <p>Supports all of the spans used by ExoPlayer's subtitle decoders, including custom ones found
+ * in {@link com.google.android.exoplayer2.text.span}.
+ */
+// TODO: Add support for more span types - only a small selection are currently implemented.
+/* package */ final class SpannedToHtmlConverter {
+
+ // Matches /n and /r/n in ampersand-encoding (returned from Html.escapeHtml).
+ private static final Pattern NEWLINE_PATTERN = Pattern.compile("( )? ");
+
+ private SpannedToHtmlConverter() {}
+
+ /**
+ * Convert {@code text} into HTML, adding tags and styling to match any styling spans present.
+ *
+ * <p>All textual content is HTML-escaped during the conversion.
+ *
+ * <p>NOTE: The current implementation does not handle overlapping spans correctly, it will
+ * generate overlapping HTML tags that are invalid. In most cases this won't be a problem because:
+ *
+ * <ul>
+ * <li>Most subtitle formats use a tagged structure to carry formatting information (e.g. WebVTT
+ * and TTML), so the {@link Spanned} objects created by these decoders likely won't have
+ * overlapping spans.
+ * <li>WebView/Chromium (the intended destination of this HTML) gracefully handles overlapping
+ * tags and usually renders the same result as spanned text in a TextView.
+ * </ul>
+ */
+ public static String convert(@Nullable CharSequence text) {
+ if (text == null) {
+ return "";
+ }
+ if (!(text instanceof Spanned)) {
+ return escapeHtml(text);
+ }
+ Spanned spanned = (Spanned) text;
+ SparseArray<Transition> spanTransitions = findSpanTransitions(spanned);
+
+ StringBuilder html = new StringBuilder(spanned.length());
+ int previousTransition = 0;
+ for (int i = 0; i < spanTransitions.size(); i++) {
+ int index = spanTransitions.keyAt(i);
+ html.append(escapeHtml(spanned.subSequence(previousTransition, index)));
+
+ Transition transition = spanTransitions.get(index);
+ Collections.sort(transition.spansRemoved, SpanInfo.FOR_CLOSING_TAGS);
+ for (SpanInfo spanInfo : transition.spansRemoved) {
+ html.append(spanInfo.closingTag);
+ }
+ Collections.sort(transition.spansAdded, SpanInfo.FOR_OPENING_TAGS);
+ for (SpanInfo spanInfo : transition.spansAdded) {
+ html.append(spanInfo.openingTag);
+ }
+ previousTransition = index;
+ }
+
+ html.append(escapeHtml(spanned.subSequence(previousTransition, spanned.length())));
+
+ return html.toString();
+ }
+
+ private static SparseArray<Transition> findSpanTransitions(Spanned spanned) {
+ SparseArray<Transition> spanTransitions = new SparseArray<>();
+
+ for (Object span : spanned.getSpans(0, spanned.length(), Object.class)) {
+ @Nullable String openingTag = getOpeningTag(span);
+ @Nullable String closingTag = getClosingTag(span);
+ int spanStart = spanned.getSpanStart(span);
+ int spanEnd = spanned.getSpanEnd(span);
+ if (openingTag != null) {
+ Assertions.checkNotNull(closingTag);
+ SpanInfo spanInfo = new SpanInfo(spanStart, spanEnd, openingTag, closingTag);
+ getOrCreate(spanTransitions, spanStart).spansAdded.add(spanInfo);
+ getOrCreate(spanTransitions, spanEnd).spansRemoved.add(spanInfo);
+ }
+ }
+
+ return spanTransitions;
+ }
+
+ @Nullable
+ private static String getOpeningTag(Object span) {
+ if (span instanceof ForegroundColorSpan) {
+ ForegroundColorSpan colorSpan = (ForegroundColorSpan) span;
+ return Util.formatInvariant(
+ "<span style='color:%s;'>", toCssColor(colorSpan.getForegroundColor()));
+ } else if (span instanceof HorizontalTextInVerticalContextSpan) {
+ return "<span style='text-combine-upright:all;'>";
+ } else if (span instanceof StyleSpan) {
+ switch (((StyleSpan) span).getStyle()) {
+ case Typeface.BOLD:
+ return "<b>";
+ case Typeface.ITALIC:
+ return "<i>";
+ case Typeface.BOLD_ITALIC:
+ return "<b><i>";
+ default:
+ return null;
+ }
+ } else if (span instanceof RubySpan) {
+ RubySpan rubySpan = (RubySpan) span;
+ switch (rubySpan.position) {
+ case RubySpan.POSITION_OVER:
+ return "<ruby style='ruby-position:over;'>";
+ case RubySpan.POSITION_UNDER:
+ return "<ruby style='ruby-position:under;'>";
+ case RubySpan.POSITION_UNKNOWN:
+ return "<ruby style='ruby-position:unset;'>";
+ default:
+ return null;
+ }
+ } else if (span instanceof UnderlineSpan) {
+ return "<u>";
+ } else {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String getClosingTag(Object span) {
+ if (span instanceof ForegroundColorSpan) {
+ return "</span>";
+ } else if (span instanceof HorizontalTextInVerticalContextSpan) {
+ return "</span>";
+ } else if (span instanceof StyleSpan) {
+ switch (((StyleSpan) span).getStyle()) {
+ case Typeface.BOLD:
+ return "</b>";
+ case Typeface.ITALIC:
+ return "</i>";
+ case Typeface.BOLD_ITALIC:
+ return "</i></b>";
+ }
+ } else if (span instanceof RubySpan) {
+ RubySpan rubySpan = (RubySpan) span;
+ return "<rt>" + escapeHtml(rubySpan.rubyText) + "</rt></ruby>";
+ } else if (span instanceof UnderlineSpan) {
+ return "</u>";
+ }
+ return null;
+ }
+
+ private static String toCssColor(@ColorInt int color) {
+ return Util.formatInvariant(
+ "rgba(%d,%d,%d,%.3f)",
+ Color.red(color), Color.green(color), Color.blue(color), Color.alpha(color) / 255.0);
+ }
+
+ private static Transition getOrCreate(SparseArray<Transition> transitions, int key) {
+ @Nullable Transition transition = transitions.get(key);
+ if (transition == null) {
+ transition = new Transition();
+ transitions.put(key, transition);
+ }
+ return transition;
+ }
+
+ private static String escapeHtml(CharSequence text) {
+ String escaped = Html.escapeHtml(text);
+ return NEWLINE_PATTERN.matcher(escaped).replaceAll("<br>");
+ }
+
+ private static final class SpanInfo {
+ /**
+ * Sort by end index (descending), then by opening tag and then closing tag (both ascending, for
+ * determinism).
+ */
+ private static final Comparator<SpanInfo> FOR_OPENING_TAGS =
+ (info1, info2) -> {
+ int result = Integer.compare(info2.end, info1.end);
+ if (result != 0) {
+ return result;
+ }
+ result = info1.openingTag.compareTo(info2.openingTag);
+ if (result != 0) {
+ return result;
+ }
+ return info1.closingTag.compareTo(info2.closingTag);
+ };
+
+ /**
+ * Sort by start index (descending), then by opening tag and then closing tag (both descending,
+ * for determinism).
+ */
+ private static final Comparator<SpanInfo> FOR_CLOSING_TAGS =
+ (info1, info2) -> {
+ int result = Integer.compare(info2.start, info1.start);
+ if (result != 0) {
+ return result;
+ }
+ result = info2.openingTag.compareTo(info1.openingTag);
+ if (result != 0) {
+ return result;
+ }
+ return info2.closingTag.compareTo(info1.closingTag);
+ };
+
+ public final int start;
+ public final int end;
+ public final String openingTag;
+ public final String closingTag;
+
+ private SpanInfo(int start, int end, String openingTag, String closingTag) {
+ this.start = start;
+ this.end = end;
+ this.openingTag = openingTag;
+ this.closingTag = closingTag;
+ }
+ }
+
+ private static final class Transition {
+ private final List<SpanInfo> spansAdded;
+ private final List<SpanInfo> spansRemoved;
+
+ public Transition() {
+ this.spansAdded = new ArrayList<>();
+ this.spansRemoved = new ArrayList<>();
+ }
+ }
+}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
index 7676880..841d1b6 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
@@ -33,6 +33,7 @@
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
@@ -98,6 +99,7 @@
// Derived drawing variables.
private @MonotonicNonNull StaticLayout textLayout;
+ private @MonotonicNonNull StaticLayout edgeLayout;
private int textLeft;
private int textTop;
private int textPaddingX;
@@ -240,7 +242,10 @@
@RequiresNonNull("cueText")
private void setupTextLayout() {
- CharSequence cueText = this.cueText;
+ SpannableStringBuilder cueText =
+ this.cueText instanceof SpannableStringBuilder
+ ? (SpannableStringBuilder) this.cueText
+ : new SpannableStringBuilder(this.cueText);
int parentWidth = parentRight - parentLeft;
int parentHeight = parentBottom - parentTop;
@@ -258,39 +263,57 @@
// Remove embedded styling or font size if requested.
if (!applyEmbeddedStyles) {
- cueText = cueText.toString(); // Equivalent to erasing all spans.
+ // Remove all spans, regardless of type.
+ for (Object span : cueText.getSpans(0, cueText.length(), Object.class)) {
+ cueText.removeSpan(span);
+ }
} else if (!applyEmbeddedFontSizes) {
- SpannableStringBuilder newCueText = new SpannableStringBuilder(cueText);
- int cueLength = newCueText.length();
- AbsoluteSizeSpan[] absSpans = newCueText.getSpans(0, cueLength, AbsoluteSizeSpan.class);
- RelativeSizeSpan[] relSpans = newCueText.getSpans(0, cueLength, RelativeSizeSpan.class);
+ AbsoluteSizeSpan[] absSpans = cueText.getSpans(0, cueText.length(), AbsoluteSizeSpan.class);
for (AbsoluteSizeSpan absSpan : absSpans) {
- newCueText.removeSpan(absSpan);
+ cueText.removeSpan(absSpan);
}
+ RelativeSizeSpan[] relSpans = cueText.getSpans(0, cueText.length(), RelativeSizeSpan.class);
for (RelativeSizeSpan relSpan : relSpans) {
- newCueText.removeSpan(relSpan);
+ cueText.removeSpan(relSpan);
}
- cueText = newCueText;
} else {
// Apply embedded styles & font size.
if (cueTextSizePx > 0) {
- // Use a SpannableStringBuilder encompassing the whole cue text to apply the default
- // cueTextSizePx.
- SpannableStringBuilder newCueText = new SpannableStringBuilder(cueText);
- newCueText.setSpan(
+ // Use an AbsoluteSizeSpan encompassing the whole text to apply the default cueTextSizePx.
+ cueText.setSpan(
new AbsoluteSizeSpan((int) cueTextSizePx),
/* start= */ 0,
- /* end= */ newCueText.length(),
+ /* end= */ cueText.length(),
Spanned.SPAN_PRIORITY);
- cueText = newCueText;
}
}
+ // Remove embedded font color to not destroy edges, otherwise it overrides edge color.
+ SpannableStringBuilder cueTextEdge = new SpannableStringBuilder(cueText);
+ if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
+ ForegroundColorSpan[] foregroundColorSpans =
+ cueTextEdge.getSpans(0, cueTextEdge.length(), ForegroundColorSpan.class);
+ for (ForegroundColorSpan foregroundColorSpan : foregroundColorSpans) {
+ cueTextEdge.removeSpan(foregroundColorSpan);
+ }
+ }
+
+ // EDGE_TYPE_NONE & EDGE_TYPE_DROP_SHADOW both paint in one pass, they ignore cueTextEdge.
+ // In other cases we use two painters and we need to apply the background in the first one only,
+ // otherwise the background color gets drawn in front of the edge color
+ // (https://github.com/google/ExoPlayer/pull/6724#issuecomment-564650572).
if (Color.alpha(backgroundColor) > 0) {
- SpannableStringBuilder newCueText = new SpannableStringBuilder(cueText);
- newCueText.setSpan(
- new BackgroundColorSpan(backgroundColor), 0, newCueText.length(), Spanned.SPAN_PRIORITY);
- cueText = newCueText;
+ if (edgeType == CaptionStyleCompat.EDGE_TYPE_NONE
+ || edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
+ cueText.setSpan(
+ new BackgroundColorSpan(backgroundColor), 0, cueText.length(), Spanned.SPAN_PRIORITY);
+ } else {
+ cueTextEdge.setSpan(
+ new BackgroundColorSpan(backgroundColor),
+ 0,
+ cueTextEdge.length(),
+ Spanned.SPAN_PRIORITY);
+ }
}
Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment;
@@ -366,6 +389,9 @@
// Update the derived drawing variables.
this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult,
spacingAdd, true);
+ this.edgeLayout =
+ new StaticLayout(
+ cueTextEdge, textPaint, textWidth, textAlignment, spacingMult, spacingAdd, true);
this.textLeft = textLeft;
this.textTop = textTop;
this.textPaddingX = textPaddingX;
@@ -405,8 +431,9 @@
}
private void drawTextLayout(Canvas canvas) {
- StaticLayout layout = textLayout;
- if (layout == null) {
+ StaticLayout textLayout = this.textLayout;
+ StaticLayout edgeLayout = this.edgeLayout;
+ if (textLayout == null || edgeLayout == null) {
// Nothing to draw.
return;
}
@@ -416,8 +443,8 @@
if (Color.alpha(windowColor) > 0) {
paint.setColor(windowColor);
- canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(),
- paint);
+ canvas.drawRect(
+ -textPaddingX, 0, textLayout.getWidth() + textPaddingX, textLayout.getHeight(), paint);
}
if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
@@ -425,7 +452,7 @@
textPaint.setStrokeWidth(outlineWidth);
textPaint.setColor(edgeColor);
textPaint.setStyle(Style.FILL_AND_STROKE);
- layout.draw(canvas);
+ edgeLayout.draw(canvas);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
@@ -437,13 +464,13 @@
textPaint.setColor(foregroundColor);
textPaint.setStyle(Style.FILL);
textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp);
- layout.draw(canvas);
+ edgeLayout.draw(canvas);
textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown);
}
textPaint.setColor(foregroundColor);
textPaint.setStyle(Style.FILL);
- layout.draw(canvas);
+ textLayout.draw(canvas);
textPaint.setShadowLayer(0, 0, 0, 0);
canvas.restoreToCount(saveCount);
@@ -466,4 +493,5 @@
// equals methods, so we perform one explicitly here.
return first == second || (first != null && first.equals(second));
}
+
}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleTextView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleTextView.java
new file mode 100644
index 0000000..39aaebc
--- /dev/null
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleTextView.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.ui;
+
+import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_BOTTOM_PADDING_FRACTION;
+import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_TEXT_SIZE_FRACTION;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.text.CaptionStyleCompat;
+import com.google.android.exoplayer2.text.Cue;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link SubtitleView.Output} that uses Android's native text tooling via {@link
+ * SubtitlePainter}.
+ */
+/* package */ final class SubtitleTextView extends View implements SubtitleView.Output {
+
+ private final List<SubtitlePainter> painters;
+
+ private List<Cue> cues;
+ @Cue.TextSizeType private int textSizeType;
+ private float textSize;
+ private boolean applyEmbeddedStyles;
+ private boolean applyEmbeddedFontSizes;
+ private CaptionStyleCompat style;
+ private float bottomPaddingFraction;
+
+ public SubtitleTextView(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public SubtitleTextView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ painters = new ArrayList<>();
+ cues = Collections.emptyList();
+ textSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL;
+ textSize = DEFAULT_TEXT_SIZE_FRACTION;
+ applyEmbeddedStyles = true;
+ applyEmbeddedFontSizes = true;
+ style = CaptionStyleCompat.DEFAULT;
+ bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION;
+ }
+
+ @Override
+ public void onCues(List<Cue> cues) {
+ if (this.cues == cues || this.cues.isEmpty() && cues.isEmpty()) {
+ return;
+ }
+ this.cues = cues;
+ // Ensure we have sufficient painters.
+ while (painters.size() < cues.size()) {
+ painters.add(new SubtitlePainter(getContext()));
+ }
+ // Invalidate to trigger drawing.
+ invalidate();
+ }
+
+ @Override
+ public void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
+ if (this.textSizeType == textSizeType && this.textSize == textSize) {
+ return;
+ }
+ this.textSizeType = textSizeType;
+ this.textSize = textSize;
+ invalidate();
+ }
+
+ @Override
+ public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
+ if (this.applyEmbeddedStyles == applyEmbeddedStyles
+ && this.applyEmbeddedFontSizes == applyEmbeddedStyles) {
+ return;
+ }
+ this.applyEmbeddedStyles = applyEmbeddedStyles;
+ this.applyEmbeddedFontSizes = applyEmbeddedStyles;
+ invalidate();
+ }
+
+ @Override
+ public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
+ if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) {
+ return;
+ }
+ this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
+ invalidate();
+ }
+
+ @Override
+ public void setStyle(CaptionStyleCompat style) {
+ if (this.style == style) {
+ return;
+ }
+ this.style = style;
+ invalidate();
+ }
+
+ @Override
+ public void setBottomPaddingFraction(float bottomPaddingFraction) {
+ if (this.bottomPaddingFraction == bottomPaddingFraction) {
+ return;
+ }
+ this.bottomPaddingFraction = bottomPaddingFraction;
+ invalidate();
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ @Nullable List<Cue> cues = this.cues;
+ if (cues.isEmpty()) {
+ return;
+ }
+
+ int rawViewHeight = getHeight();
+
+ // Calculate the cue box bounds relative to the canvas after padding is taken into account.
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ int right = getWidth() - getPaddingRight();
+ int bottom = rawViewHeight - getPaddingBottom();
+ if (bottom <= top || right <= left) {
+ // No space to draw subtitles.
+ return;
+ }
+ int viewHeightMinusPadding = bottom - top;
+
+ float defaultViewTextSizePx =
+ SubtitleViewUtils.resolveTextSize(
+ textSizeType, textSize, rawViewHeight, viewHeightMinusPadding);
+ if (defaultViewTextSizePx <= 0) {
+ // Text has no height.
+ return;
+ }
+
+ int cueCount = cues.size();
+ for (int i = 0; i < cueCount; i++) {
+ Cue cue = cues.get(i);
+ float cueTextSizePx =
+ SubtitleViewUtils.resolveCueTextSize(cue, rawViewHeight, viewHeightMinusPadding);
+ SubtitlePainter painter = painters.get(i);
+ painter.draw(
+ cue,
+ applyEmbeddedStyles,
+ applyEmbeddedFontSizes,
+ style,
+ defaultViewTextSizePx,
+ cueTextSizePx,
+ bottomPaddingFraction,
+ canvas,
+ left,
+ top,
+ right,
+ bottom);
+ }
+ }
+}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
index 5fa2e48..775045c 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
@@ -12,34 +12,39 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
package com.google.android.exoplayer2.ui;
-import android.annotation.TargetApi;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.CaptioningManager;
+import androidx.annotation.Dimension;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.util.Util;
-import java.util.ArrayList;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.util.Collections;
import java.util.List;
-/**
- * A view for displaying subtitle {@link Cue}s.
- */
-public final class SubtitleView extends View implements TextOutput {
+/** A view for displaying subtitle {@link Cue}s. */
+public final class SubtitleView extends ViewGroup implements TextOutput {
/**
* The default fractional text size.
*
- * @see #setFractionalTextSize(float, boolean)
+ * @see SubtitleView#setFractionalTextSize(float, boolean)
*/
public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f;
@@ -51,29 +56,50 @@
*/
public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f;
- private final List<SubtitlePainter> painters;
+ /**
+ * Indicates a {@link SubtitleTextView} should be used to display subtitles. This is the default.
+ */
+ public static final int VIEW_TYPE_TEXT = 1;
- @Nullable private List<Cue> cues;
- @Cue.TextSizeType private int textSizeType;
- private float textSize;
- private boolean applyEmbeddedStyles;
- private boolean applyEmbeddedFontSizes;
- private CaptionStyleCompat style;
- private float bottomPaddingFraction;
+ /**
+ * Indicates a {@link SubtitleWebView} should be used to display subtitles.
+ *
+ * <p>This will instantiate a {@link android.webkit.WebView} and use CSS and HTML styling to
+ * render the subtitles. This supports some additional styling features beyond those supported by
+ * {@link SubtitleTextView} such as vertical text.
+ */
+ public static final int VIEW_TYPE_WEB = 2;
+
+ /**
+ * The type of {@link View} to use to display subtitles.
+ *
+ * <p>One of:
+ *
+ * <ul>
+ * <li>{@link #VIEW_TYPE_TEXT}
+ * <li>{@link #VIEW_TYPE_WEB}
+ * </ul>
+ */
+ @Documented
+ @Retention(SOURCE)
+ @IntDef({VIEW_TYPE_TEXT, VIEW_TYPE_WEB})
+ public @interface ViewType {}
+
+ private @ViewType int viewType;
+ private Output output;
+ private View innerSubtitleView;
public SubtitleView(Context context) {
- this(context, /* attrs= */ null);
+ this(context, null);
}
public SubtitleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- painters = new ArrayList<>();
- textSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL;
- textSize = DEFAULT_TEXT_SIZE_FRACTION;
- applyEmbeddedStyles = true;
- applyEmbeddedFontSizes = true;
- style = CaptionStyleCompat.DEFAULT;
- bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION;
+ SubtitleTextView subtitleTextView = new SubtitleTextView(context, attrs);
+ output = subtitleTextView;
+ innerSubtitleView = subtitleTextView;
+ addView(innerSubtitleView);
+ viewType = VIEW_TYPE_TEXT;
}
@Override
@@ -87,28 +113,57 @@
* @param cues The cues to display, or null to clear the cues.
*/
public void setCues(@Nullable List<Cue> cues) {
- if (this.cues == cues) {
+ output.onCues(cues != null ? cues : Collections.emptyList());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (changed) {
+ innerSubtitleView.layout(l, t, r, b);
+ }
+ }
+
+ /**
+ * Set the type of {@link View} used to display subtitles.
+ *
+ * <p>NOTE: {@link #VIEW_TYPE_WEB} is currently very experimental, and doesn't support most
+ * styling and layout properties of {@link Cue}.
+ *
+ * @param viewType The {@link ViewType} to use.
+ */
+ public void setViewType(@ViewType int viewType) {
+ if (this.viewType == viewType) {
return;
}
- this.cues = cues;
- // Ensure we have sufficient painters.
- int cueCount = (cues == null) ? 0 : cues.size();
- while (painters.size() < cueCount) {
- painters.add(new SubtitlePainter(getContext()));
+ switch (viewType) {
+ case VIEW_TYPE_TEXT:
+ setView(new SubtitleTextView(getContext()));
+ break;
+ case VIEW_TYPE_WEB:
+ setView(new SubtitleWebView(getContext()));
+ break;
+ default:
+ throw new IllegalArgumentException();
}
- // Invalidate to trigger drawing.
- invalidate();
+ this.viewType = viewType;
+ }
+
+ private <T extends View & Output> void setView(T view) {
+ removeView(innerSubtitleView);
+ innerSubtitleView = view;
+ output = view;
+ addView(view);
}
/**
* Set the text size to a given unit and value.
- * <p>
- * See {@link TypedValue} for the possible dimension units.
+ *
+ * <p>See {@link TypedValue} for the possible dimension units.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
*/
- public void setFixedTextSize(int unit, float size) {
+ public void setFixedTextSize(@Dimension int unit, float size) {
Context context = getContext();
Resources resources;
if (context == null) {
@@ -160,13 +215,7 @@
}
private void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
- if (this.textSizeType == textSizeType && this.textSize == textSize) {
- return;
- }
- this.textSizeType = textSizeType;
- this.textSize = textSize;
- // Invalidate to trigger drawing.
- invalidate();
+ output.setTextSize(textSizeType, textSize);
}
/**
@@ -176,14 +225,7 @@
* @param applyEmbeddedStyles Whether styling embedded within the cues should be applied.
*/
public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
- if (this.applyEmbeddedStyles == applyEmbeddedStyles
- && this.applyEmbeddedFontSizes == applyEmbeddedStyles) {
- return;
- }
- this.applyEmbeddedStyles = applyEmbeddedStyles;
- this.applyEmbeddedFontSizes = applyEmbeddedStyles;
- // Invalidate to trigger drawing.
- invalidate();
+ output.setApplyEmbeddedStyles(applyEmbeddedStyles);
}
/**
@@ -193,12 +235,7 @@
* @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied.
*/
public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
- if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) {
- return;
- }
- this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
- // Invalidate to trigger drawing.
- invalidate();
+ output.setApplyEmbeddedFontSizes(applyEmbeddedFontSizes);
}
/**
@@ -218,12 +255,7 @@
* @param style A style for the view.
*/
public void setStyle(CaptionStyleCompat style) {
- if (this.style == style) {
- return;
- }
- this.style = style;
- // Invalidate to trigger drawing.
- invalidate();
+ output.setStyle(style);
}
/**
@@ -236,108 +268,36 @@
* @param bottomPaddingFraction The bottom padding fraction.
*/
public void setBottomPaddingFraction(float bottomPaddingFraction) {
- if (this.bottomPaddingFraction == bottomPaddingFraction) {
- return;
- }
- this.bottomPaddingFraction = bottomPaddingFraction;
- // Invalidate to trigger drawing.
- invalidate();
+ output.setBottomPaddingFraction(bottomPaddingFraction);
}
- @Override
- public void dispatchDraw(Canvas canvas) {
- List<Cue> cues = this.cues;
- if (cues == null || cues.isEmpty()) {
- return;
- }
-
- int rawViewHeight = getHeight();
-
- // Calculate the cue box bounds relative to the canvas after padding is taken into account.
- int left = getPaddingLeft();
- int top = getPaddingTop();
- int right = getWidth() - getPaddingRight();
- int bottom = rawViewHeight - getPaddingBottom();
- if (bottom <= top || right <= left) {
- // No space to draw subtitles.
- return;
- }
- int viewHeightMinusPadding = bottom - top;
-
- float defaultViewTextSizePx =
- resolveTextSize(textSizeType, textSize, rawViewHeight, viewHeightMinusPadding);
- if (defaultViewTextSizePx <= 0) {
- // Text has no height.
- return;
- }
-
- int cueCount = cues.size();
- for (int i = 0; i < cueCount; i++) {
- Cue cue = cues.get(i);
- float cueTextSizePx = resolveCueTextSize(cue, rawViewHeight, viewHeightMinusPadding);
- SubtitlePainter painter = painters.get(i);
- painter.draw(
- cue,
- applyEmbeddedStyles,
- applyEmbeddedFontSizes,
- style,
- defaultViewTextSizePx,
- cueTextSizePx,
- bottomPaddingFraction,
- canvas,
- left,
- top,
- right,
- bottom);
- }
- }
-
- private float resolveCueTextSize(Cue cue, int rawViewHeight, int viewHeightMinusPadding) {
- if (cue.textSizeType == Cue.TYPE_UNSET || cue.textSize == Cue.DIMEN_UNSET) {
- return 0;
- }
- float defaultCueTextSizePx =
- resolveTextSize(cue.textSizeType, cue.textSize, rawViewHeight, viewHeightMinusPadding);
- return Math.max(defaultCueTextSizePx, 0);
- }
-
- private float resolveTextSize(
- @Cue.TextSizeType int textSizeType,
- float textSize,
- int rawViewHeight,
- int viewHeightMinusPadding) {
- switch (textSizeType) {
- case Cue.TEXT_SIZE_TYPE_ABSOLUTE:
- return textSize;
- case Cue.TEXT_SIZE_TYPE_FRACTIONAL:
- return textSize * viewHeightMinusPadding;
- case Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING:
- return textSize * rawViewHeight;
- case Cue.TYPE_UNSET:
- default:
- return Cue.DIMEN_UNSET;
- }
- }
-
- @TargetApi(19)
+ @RequiresApi(19)
private boolean isCaptionManagerEnabled() {
CaptioningManager captioningManager =
(CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
return captioningManager.isEnabled();
}
- @TargetApi(19)
+ @RequiresApi(19)
private float getUserCaptionFontScaleV19() {
CaptioningManager captioningManager =
(CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
return captioningManager.getFontScale();
}
- @TargetApi(19)
+ @RequiresApi(19)
private CaptionStyleCompat getUserCaptionStyleV19() {
CaptioningManager captioningManager =
(CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
+ /* package */ interface Output {
+ void onCues(List<Cue> cues);
+ void setTextSize(@Cue.TextSizeType int textSizeType, float textSize);
+ void setApplyEmbeddedStyles(boolean applyEmbeddedStyles);
+ void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes);
+ void setStyle(CaptionStyleCompat style);
+ void setBottomPaddingFraction(float bottomPaddingFraction);
+ }
}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java
new file mode 100644
index 0000000..4c1eb32
--- /dev/null
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.ui;
+
+import com.google.android.exoplayer2.text.Cue;
+
+/** Utility class for subtitle layout logic. */
+/* package */ final class SubtitleViewUtils {
+
+ public static float resolveCueTextSize(Cue cue, int rawViewHeight, int viewHeightMinusPadding) {
+ if (cue.textSizeType == Cue.TYPE_UNSET || cue.textSize == Cue.DIMEN_UNSET) {
+ return 0;
+ }
+ float defaultCueTextSizePx =
+ resolveTextSize(cue.textSizeType, cue.textSize, rawViewHeight, viewHeightMinusPadding);
+ return Math.max(defaultCueTextSizePx, 0);
+ }
+
+ public static float resolveTextSize(
+ @Cue.TextSizeType int textSizeType,
+ float textSize,
+ int rawViewHeight,
+ int viewHeightMinusPadding) {
+ switch (textSizeType) {
+ case Cue.TEXT_SIZE_TYPE_ABSOLUTE:
+ return textSize;
+ case Cue.TEXT_SIZE_TYPE_FRACTIONAL:
+ return textSize * viewHeightMinusPadding;
+ case Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING:
+ return textSize * rawViewHeight;
+ case Cue.TYPE_UNSET:
+ default:
+ return Cue.DIMEN_UNSET;
+ }
+ }
+
+ private SubtitleViewUtils() {}
+}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java
new file mode 100644
index 0000000..670314e
--- /dev/null
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.ui;
+
+import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_BOTTOM_PADDING_FRACTION;
+import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_TEXT_SIZE_FRACTION;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Base64;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.text.CaptionStyleCompat;
+import com.google.android.exoplayer2.text.Cue;
+import com.google.android.exoplayer2.util.Util;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link SubtitleView.Output} that uses a {@link WebView} to render subtitles.
+ *
+ * <p>This is useful for subtitle styling not supported by Android's native text libraries such as
+ * vertical text.
+ *
+ * <p>NOTE: This is currently extremely experimental and doesn't support most {@link Cue} styling
+ * properties.
+ */
+/* package */ final class SubtitleWebView extends ViewGroup implements SubtitleView.Output {
+
+ private final WebView webView;
+
+ private List<Cue> cues;
+ @Cue.TextSizeType private int textSizeType;
+ private float textSize;
+ private boolean applyEmbeddedStyles;
+ private boolean applyEmbeddedFontSizes;
+ private CaptionStyleCompat style;
+ private float bottomPaddingFraction;
+
+ public SubtitleWebView(Context context) {
+ this(context, null);
+ }
+
+ public SubtitleWebView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ cues = Collections.emptyList();
+ textSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL;
+ textSize = DEFAULT_TEXT_SIZE_FRACTION;
+ applyEmbeddedStyles = true;
+ applyEmbeddedFontSizes = true;
+ style = CaptionStyleCompat.DEFAULT;
+ bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION;
+
+ webView =
+ new WebView(context, attrs) {
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ // Return false so that touch events are allowed down into @id/exo_content_frame below.
+ return false;
+ }
+
+ @Override
+ public boolean performClick() {
+ super.performClick();
+ // Return false so that clicks are allowed down into @id/exo_content_frame below.
+ return false;
+ }
+ };
+ webView.setBackgroundColor(Color.TRANSPARENT);
+ addView(webView);
+ }
+
+ @Override
+ public void onCues(List<Cue> cues) {
+ this.cues = cues;
+ updateWebView();
+ }
+
+ @Override
+ public void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
+ if (this.textSizeType == textSizeType && this.textSize == textSize) {
+ return;
+ }
+ this.textSizeType = textSizeType;
+ this.textSize = textSize;
+ updateWebView();
+ }
+
+ @Override
+ public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
+ if (this.applyEmbeddedStyles == applyEmbeddedStyles
+ && this.applyEmbeddedFontSizes == applyEmbeddedStyles) {
+ return;
+ }
+ this.applyEmbeddedStyles = applyEmbeddedStyles;
+ this.applyEmbeddedFontSizes = applyEmbeddedStyles;
+ updateWebView();
+ }
+
+ @Override
+ public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
+ if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) {
+ return;
+ }
+ this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
+ updateWebView();
+ }
+
+ @Override
+ public void setStyle(CaptionStyleCompat style) {
+ if (this.style == style) {
+ return;
+ }
+ this.style = style;
+ updateWebView();
+ }
+
+ @Override
+ public void setBottomPaddingFraction(float bottomPaddingFraction) {
+ if (this.bottomPaddingFraction == bottomPaddingFraction) {
+ return;
+ }
+ this.bottomPaddingFraction = bottomPaddingFraction;
+ updateWebView();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (changed) {
+ webView.layout(l, t, r, b);
+ }
+ }
+
+ private void updateWebView() {
+ StringBuilder html = new StringBuilder();
+ html.append("<html><body>")
+ .append("<div style=\"")
+ .append("-webkit-user-select:none;")
+ .append("position:fixed;")
+ .append("top:0;")
+ .append("bottom:0;")
+ .append("left:0;")
+ .append("right:0;")
+ .append("font-size:20px;")
+ .append("color:red;")
+ .append("\">");
+
+ for (int i = 0; i < cues.size(); i++) {
+ Cue cue = cues.get(i);
+ float positionPercent = (cue.position != Cue.DIMEN_UNSET) ? (cue.position * 100) : 50;
+ int positionAnchorTranslatePercent = anchorTypeToTranslatePercent(cue.positionAnchor);
+
+ float linePercent;
+ int lineTranslatePercent;
+ if (cue.line != Cue.DIMEN_UNSET) {
+ switch (cue.lineType) {
+ case Cue.LINE_TYPE_NUMBER:
+ if (cue.line >= 0) {
+ linePercent = 0;
+ lineTranslatePercent = Math.round(cue.line) * 100;
+ } else {
+ linePercent = 100;
+ lineTranslatePercent = Math.round(cue.line + 1) * 100;
+ }
+ break;
+ case Cue.LINE_TYPE_FRACTION:
+ case Cue.TYPE_UNSET:
+ default:
+ linePercent = cue.line * 100;
+ lineTranslatePercent = 0;
+ }
+ } else {
+ linePercent = 100;
+ lineTranslatePercent = 0;
+ }
+ int lineAnchorTranslatePercent =
+ cue.verticalType == Cue.VERTICAL_TYPE_RL
+ ? -anchorTypeToTranslatePercent(cue.lineAnchor)
+ : anchorTypeToTranslatePercent(cue.lineAnchor);
+
+ String size =
+ cue.size != Cue.DIMEN_UNSET
+ ? Util.formatInvariant("%.2f%%", cue.size * 100)
+ : "fit-content";
+
+ String textAlign = convertAlignmentToCss(cue.textAlignment);
+
+ String writingMode = convertVerticalTypeToCss(cue.verticalType);
+
+ String positionProperty;
+ String lineProperty;
+ switch (cue.verticalType) {
+ case Cue.VERTICAL_TYPE_LR:
+ lineProperty = "left";
+ positionProperty = "top";
+ break;
+ case Cue.VERTICAL_TYPE_RL:
+ lineProperty = "right";
+ positionProperty = "top";
+ break;
+ case Cue.TYPE_UNSET:
+ default:
+ lineProperty = "top";
+ positionProperty = "left";
+ }
+
+ String sizeProperty;
+ int horizontalTranslatePercent;
+ int verticalTranslatePercent;
+ if (cue.verticalType == Cue.VERTICAL_TYPE_LR || cue.verticalType == Cue.VERTICAL_TYPE_RL) {
+ sizeProperty = "height";
+ horizontalTranslatePercent = lineTranslatePercent + lineAnchorTranslatePercent;
+ verticalTranslatePercent = positionAnchorTranslatePercent;
+ } else {
+ sizeProperty = "width";
+ horizontalTranslatePercent = positionAnchorTranslatePercent;
+ verticalTranslatePercent = lineTranslatePercent + lineAnchorTranslatePercent;
+ }
+
+ html.append(
+ Util.formatInvariant(
+ "<div style=\""
+ + "position:absolute;"
+ + "%s:%.2f%%;"
+ + "%s:%.2f%%;"
+ + "%s:%s;"
+ + "text-align:%s;"
+ + "writing-mode:%s;"
+ + "transform:translate(%s%%,%s%%);"
+ + "\">",
+ positionProperty,
+ positionPercent,
+ lineProperty,
+ linePercent,
+ sizeProperty,
+ size,
+ textAlign,
+ writingMode,
+ horizontalTranslatePercent,
+ verticalTranslatePercent))
+ .append(SpannedToHtmlConverter.convert(cue.text))
+ .append("</div>");
+ }
+
+ html.append("</div></body></html>");
+
+ webView.loadData(
+ Base64.encodeToString(
+ html.toString().getBytes(Charset.forName(C.UTF8_NAME)), Base64.NO_PADDING),
+ "text/html",
+ "base64");
+ }
+
+ private String convertVerticalTypeToCss(@Cue.VerticalType int verticalType) {
+ switch (verticalType) {
+ case Cue.VERTICAL_TYPE_LR:
+ return "vertical-lr";
+ case Cue.VERTICAL_TYPE_RL:
+ return "vertical-rl";
+ case Cue.TYPE_UNSET:
+ default:
+ return "horizontal-tb";
+ }
+ }
+
+ private String convertAlignmentToCss(@Nullable Layout.Alignment alignment) {
+ if (alignment == null) {
+ return "unset";
+ }
+ switch (alignment) {
+ case ALIGN_NORMAL:
+ return "start";
+ case ALIGN_CENTER:
+ return "center";
+ case ALIGN_OPPOSITE:
+ return "end";
+ default:
+ return "unset";
+ }
+ }
+
+ /**
+ * Converts a {@link Cue.AnchorType} to a percentage for use in a CSS {@code transform:
+ * translate(x,y)} function.
+ *
+ * <p>We use {@code position: absolute} and always use the same CSS positioning property (top,
+ * bottom, left, right) regardless of the anchor type. The anchor is effectively 'moved' by using
+ * a CSS {@code translate(x,y)} operation on the value returned from this function.
+ */
+ private static int anchorTypeToTranslatePercent(@Cue.AnchorType int anchorType) {
+ switch (anchorType) {
+ case Cue.ANCHOR_TYPE_END:
+ return -100;
+ case Cue.ANCHOR_TYPE_MIDDLE:
+ return -50;
+ case Cue.ANCHOR_TYPE_START:
+ case Cue.TYPE_UNSET:
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java
index c01fccf..1c96f41 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalGLSurfaceView.java
@@ -72,6 +72,9 @@
@Nullable private SurfaceTexture surfaceTexture;
@Nullable private Surface surface;
@Nullable private Player.VideoComponent videoComponent;
+ private boolean useSensorRotation;
+ private boolean isStarted;
+ private boolean isOrientationListenerRegistered;
public SphericalGLSurfaceView(Context context) {
this(context, null);
@@ -104,6 +107,7 @@
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay();
orientationListener = new OrientationListener(display, touchTracker, renderer);
+ useSensorRotation = true;
setEGLContextClientVersion(2);
setRenderer(renderer);
@@ -145,20 +149,23 @@
touchTracker.setSingleTapListener(listener);
}
+ /** Sets whether to use the orientation sensor for rotation (if available). */
+ public void setUseSensorRotation(boolean useSensorRotation) {
+ this.useSensorRotation = useSensorRotation;
+ updateOrientationListenerRegistration();
+ }
+
@Override
public void onResume() {
super.onResume();
- if (orientationSensor != null) {
- sensorManager.registerListener(
- orientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
- }
+ isStarted = true;
+ updateOrientationListenerRegistration();
}
@Override
public void onPause() {
- if (orientationSensor != null) {
- sensorManager.unregisterListener(orientationListener);
- }
+ isStarted = false;
+ updateOrientationListenerRegistration();
super.onPause();
}
@@ -181,6 +188,20 @@
});
}
+ private void updateOrientationListenerRegistration() {
+ boolean enabled = useSensorRotation && isStarted;
+ if (orientationSensor == null || enabled == isOrientationListenerRegistered) {
+ return;
+ }
+ if (enabled) {
+ sensorManager.registerListener(
+ orientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
+ } else {
+ sensorManager.unregisterListener(orientationListener);
+ }
+ isOrientationListenerRegistered = enabled;
+ }
+
// Called on GL thread.
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
mainHandler.post(
diff --git a/tree/library/ui/src/main/res/values/attrs.xml b/tree/library/ui/src/main/res/values/attrs.xml
index 535bf32..e0a6b7f 100644
--- a/tree/library/ui/src/main/res/values/attrs.xml
+++ b/tree/library/ui/src/main/res/values/attrs.xml
@@ -77,8 +77,8 @@
<enum name="always" value="2"/>
</attr>
<attr name="keep_content_on_player_reset" format="boolean"/>
+ <attr name="use_sensor_rotation" format="boolean"/>
<attr name="player_layout_id" format="reference"/>
-
<attr name="surface_type"/>
<!-- AspectRatioFrameLayout attributes -->
<attr name="resize_mode"/>
diff --git a/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java b/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
new file mode 100644
index 0000000..5e572fa
--- /dev/null
+++ b/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.ui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
+import com.google.android.exoplayer2.text.span.RubySpan;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SpannedToHtmlConverter}. */
+@RunWith(AndroidJUnit4.class)
+public class SpannedToHtmlConverterTest {
+
+ @Test
+ public void convert_supportsForegroundColorSpan() {
+ SpannableString spanned = new SpannableString("String with colored section");
+ spanned.setSpan(
+ new ForegroundColorSpan(Color.argb(128, 64, 32, 16)),
+ "String with ".length(),
+ "String with colored".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo("String with <span style='color:rgba(64,32,16,0.502);'>colored</span> section");
+ }
+
+ @Test
+ public void convert_supportsHorizontalTextInVerticalContextSpan() {
+ SpannableString spanned = new SpannableString("Vertical text with 123 horizontal numbers");
+ spanned.setSpan(
+ new HorizontalTextInVerticalContextSpan(),
+ "Vertical text with ".length(),
+ "Vertical text with 123".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "Vertical text with <span style='text-combine-upright:all;'>123</span> "
+ + "horizontal numbers");
+ }
+
+ @Test
+ public void convert_supportsStyleSpan() {
+ SpannableString spanned =
+ new SpannableString("String with bold, italic and bold-italic sections.");
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ "String with ".length(),
+ "String with bold".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.ITALIC),
+ "String with bold, ".length(),
+ "String with bold, italic".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD_ITALIC),
+ "String with bold, italic and ".length(),
+ "String with bold, italic and bold-italic".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "String with <b>bold</b>, <i>italic</i> and <b><i>bold-italic</i></b> sections.");
+ }
+
+ @Test
+ public void convert_supportsRubySpan() {
+ SpannableString spanned =
+ new SpannableString("String with over-annotated and under-annotated section");
+ spanned.setSpan(
+ new RubySpan("ruby-text", RubySpan.POSITION_OVER),
+ "String with ".length(),
+ "String with over-annotated".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new RubySpan("non-àscìì-text", RubySpan.POSITION_UNDER),
+ "String with over-annotated and ".length(),
+ "String with over-annotated and under-annotated".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "String with "
+ + "<ruby style='ruby-position:over;'>"
+ + "over-annotated"
+ + "<rt>ruby-text</rt>"
+ + "</ruby> "
+ + "and "
+ + "<ruby style='ruby-position:under;'>"
+ + "under-annotated"
+ + "<rt>non-àscìì-text</rt>"
+ + "</ruby> "
+ + "section");
+ }
+
+ @Test
+ public void convert_supportsUnderlineSpan() {
+ SpannableString spanned = new SpannableString("String with underlined section.");
+ spanned.setSpan(
+ new UnderlineSpan(),
+ "String with ".length(),
+ "String with underlined".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with <u>underlined</u> section.");
+ }
+
+ @Test
+ public void convert_escapesHtmlInUnspannedString() {
+ String html = SpannedToHtmlConverter.convert("String with <b>bold</b> tags");
+
+ assertThat(html).isEqualTo("String with <b>bold</b> tags");
+ }
+
+ @Test
+ public void convert_handlesLinebreakInUnspannedString() {
+ String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too");
+
+ assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too");
+ }
+
+ @Test
+ public void convert_doesntConvertAmpersandLineFeedToBrTag() {
+ String html = SpannedToHtmlConverter.convert("String with new line ampersand code");
+
+ assertThat(html).isEqualTo("String with&#10;new line ampersand code");
+ }
+
+ @Test
+ public void convert_escapesUnrecognisedTagInSpannedString() {
+ SpannableString spanned = new SpannableString("String with <foo>unrecognised</foo> tags");
+ spanned.setSpan(
+ new StyleSpan(Typeface.ITALIC),
+ "String with ".length(),
+ "String with <foo>unrecognised</foo>".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with <i><foo>unrecognised</foo></i> tags");
+ }
+
+ @Test
+ public void convert_handlesLinebreakInSpannedString() {
+ String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too");
+
+ assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too");
+ }
+
+ @Test
+ public void convert_convertsNonAsciiCharactersToAmpersandCodes() {
+ String html =
+ SpannedToHtmlConverter.convert(
+ new SpannableString("Strìng with 優しいの non-ASCII characters"));
+
+ assertThat(html)
+ .isEqualTo("Strìng with 優しいの non-ASCII characters");
+ }
+
+ @Test
+ public void convert_ignoresUnrecognisedSpan() {
+ SpannableString spanned = new SpannableString("String with unrecognised span");
+ spanned.setSpan(
+ new Object() {
+ @Override
+ public String toString() {
+ return "Force an anonymous class to be created";
+ }
+ },
+ "String with ".length(),
+ "String with unrecognised".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with unrecognised span");
+ }
+
+ @Test
+ public void convert_sortsTagsConsistently() {
+ SpannableString spanned = new SpannableString("String with italic-bold-underlined section");
+ int start = "String with ".length();
+ int end = "String with italic-bold-underlined".length();
+ spanned.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with <b><i><u>italic-bold-underlined</u></i></b> section");
+ }
+
+ @Test
+ public void convert_supportsNestedTags() {
+ SpannableString spanned = new SpannableString("String with italic and bold section");
+ int start = "String with ".length();
+ int end = "String with italic and bold".length();
+ spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ "String with italic and ".length(),
+ "String with italic and bold".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with <i>italic and <b>bold</b></i> section");
+ }
+
+ @Test
+ public void convert_overlappingSpans_producesInvalidHtml() {
+ SpannableString spanned = new SpannableString("String with italic and bold section");
+ spanned.setSpan(
+ new StyleSpan(Typeface.ITALIC),
+ 0,
+ "String with italic and bold".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ "String with italic ".length(),
+ "String with italic and bold section".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("<i>String with italic <b>and bold</i> section</b>");
+ }
+}
diff --git a/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java b/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java
index 6212a74..8147ae8 100644
--- a/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java
+++ b/tree/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java
@@ -59,7 +59,7 @@
}
@Test
- public void testTap() {
+ public void tap() {
// Tap is a noop.
swipe(tracker, 0, 0, 0, 0);
assertThat(yaw).isWithin(EPSILON).of(0);
@@ -67,21 +67,21 @@
}
@Test
- public void testBasicYaw() {
+ public void basicYaw() {
swipe(tracker, 0, 0, SWIPE_PX, 0);
assertThat(yaw).isWithin(EPSILON).of(-SWIPE_PX / PX_PER_DEGREES);
assertThat(pitch).isWithin(EPSILON).of(0);
}
@Test
- public void testBigYaw() {
+ public void bigYaw() {
swipe(tracker, 0, 0, -10 * SWIPE_PX, 0);
assertThat(yaw).isEqualTo(10 * SWIPE_PX / PX_PER_DEGREES);
assertThat(pitch).isWithin(EPSILON).of(0);
}
@Test
- public void testYawUnaffectedByPitch() {
+ public void yawUnaffectedByPitch() {
swipe(tracker, 0, 0, 0, SWIPE_PX);
assertThat(yaw).isWithin(EPSILON).of(0);
@@ -90,14 +90,14 @@
}
@Test
- public void testBasicPitch() {
+ public void basicPitch() {
swipe(tracker, 0, 0, 0, SWIPE_PX);
assertThat(yaw).isWithin(EPSILON).of(0);
assertThat(pitch).isWithin(EPSILON).of(SWIPE_PX / PX_PER_DEGREES);
}
@Test
- public void testPitchClipped() {
+ public void pitchClipped() {
// Big reverse pitch should be clipped.
swipe(tracker, 0, 0, 0, -20 * SWIPE_PX);
assertThat(yaw).isWithin(EPSILON).of(0);
@@ -110,7 +110,7 @@
}
@Test
- public void testWithRoll90() {
+ public void withRoll90() {
tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(90));
// Y-axis should now control yaw.
@@ -123,7 +123,7 @@
}
@Test
- public void testWithRoll180() {
+ public void withRoll180() {
tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(180));
// X-axis should now control reverse yaw.
@@ -136,7 +136,7 @@
}
@Test
- public void testWithRoll270() {
+ public void withRoll270() {
tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(270));
// Y-axis should now control reverse yaw.
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
index a01ab3a..d25836e 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
@@ -63,7 +63,7 @@
}
@Test
- public void testCencSchemeTypeV18() {
+ public void cencSchemeTypeV18() {
if (Util.SDK_INT < 18) {
// Pass.
return;
@@ -75,7 +75,7 @@
}
@Test
- public void testCbc1SchemeTypeV25() {
+ public void cbc1SchemeTypeV25() {
if (Util.SDK_INT < 25) {
// cbc1 support was added in API 24, but it is stable from API 25 onwards.
// See [internal: b/65634809].
@@ -89,7 +89,7 @@
}
@Test
- public void testCbcsSchemeTypeV25() {
+ public void cbcsSchemeTypeV25() {
if (Util.SDK_INT < 25) {
// cbcs support was added in API 24, but it is stable from API 25 onwards.
// See [internal: b/65634809].
@@ -103,7 +103,7 @@
}
@Test
- public void testCensSchemeTypeV25() {
+ public void censSchemeTypeV25() {
// TODO: Implement once content is available. Track [internal: b/31219813].
}
}
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java
index f5af247..c2e14ab 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java
@@ -87,7 +87,7 @@
// Download tests
@Test
- public void testDownload() throws Exception {
+ public void download() throws Exception {
DashDownloader dashDownloader = downloadContent();
dashDownloader.download(/* progressListener= */ null);
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
index 5ae4708..259c2e6 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
@@ -101,7 +101,7 @@
// H264 CDD.
@Test
- public void testH264Fixed() throws Exception {
+ public void h264Fixed() throws Exception {
testRunner
.setStreamName("test_h264_fixed")
.setManifestUrl(DashTestData.H264_MANIFEST)
@@ -112,7 +112,7 @@
}
@Test
- public void testH264Adaptive() throws Exception {
+ public void h264Adaptive() throws Exception {
if (shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -128,7 +128,7 @@
}
@Test
- public void testH264AdaptiveWithSeeking() throws Exception {
+ public void h264AdaptiveWithSeeking() throws Exception {
if (shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -146,7 +146,7 @@
}
@Test
- public void testH264AdaptiveWithRendererDisabling() throws Exception {
+ public void h264AdaptiveWithRendererDisabling() throws Exception {
if (shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -166,7 +166,7 @@
// H265 CDD.
@Test
- public void testH265FixedV23() throws Exception {
+ public void h265FixedV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -181,7 +181,7 @@
}
@Test
- public void testH265AdaptiveV24() throws Exception {
+ public void h265AdaptiveV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -197,7 +197,7 @@
}
@Test
- public void testH265AdaptiveWithSeekingV24() throws Exception {
+ public void h265AdaptiveWithSeekingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -214,7 +214,7 @@
}
@Test
- public void testH265AdaptiveWithRendererDisablingV24() throws Exception {
+ public void h265AdaptiveWithRendererDisablingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -233,7 +233,7 @@
// VP9 (CDD).
@Test
- public void testVp9Fixed360pV23() throws Exception {
+ public void vp9Fixed360pV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -249,7 +249,7 @@
}
@Test
- public void testVp9AdaptiveV24() throws Exception {
+ public void vp9AdaptiveV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -265,7 +265,7 @@
}
@Test
- public void testVp9AdaptiveWithSeekingV24() throws Exception {
+ public void vp9AdaptiveWithSeekingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -282,7 +282,7 @@
}
@Test
- public void testVp9AdaptiveWithRendererDisablingV24() throws Exception {
+ public void vp9AdaptiveWithRendererDisablingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -355,7 +355,7 @@
// H264 CDD.
@Test
- public void testWidevineH264FixedV18() throws Exception {
+ public void widevineH264FixedV18() throws Exception {
if (Util.SDK_INT < 18) {
// Pass.
return;
@@ -372,7 +372,7 @@
}
@Test
- public void testWidevineH264AdaptiveV18() throws Exception {
+ public void widevineH264AdaptiveV18() throws Exception {
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -389,7 +389,7 @@
}
@Test
- public void testWidevineH264AdaptiveWithSeekingV18() throws Exception {
+ public void widevineH264AdaptiveWithSeekingV18() throws Exception {
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -407,7 +407,7 @@
}
@Test
- public void testWidevineH264AdaptiveWithRendererDisablingV18() throws Exception {
+ public void widevineH264AdaptiveWithRendererDisablingV18() throws Exception {
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
// Pass.
return;
@@ -427,7 +427,7 @@
// H265 CDD.
@Test
- public void testWidevineH265FixedV23() throws Exception {
+ public void widevineH265FixedV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -444,7 +444,7 @@
}
@Test
- public void testWidevineH265AdaptiveV24() throws Exception {
+ public void widevineH265AdaptiveV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -461,7 +461,7 @@
}
@Test
- public void testWidevineH265AdaptiveWithSeekingV24() throws Exception {
+ public void widevineH265AdaptiveWithSeekingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -479,7 +479,7 @@
}
@Test
- public void testWidevineH265AdaptiveWithRendererDisablingV24() throws Exception {
+ public void widevineH265AdaptiveWithRendererDisablingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -499,7 +499,7 @@
// VP9 (CDD).
@Test
- public void testWidevineVp9Fixed360pV23() throws Exception {
+ public void widevineVp9Fixed360pV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -516,7 +516,7 @@
}
@Test
- public void testWidevineVp9AdaptiveV24() throws Exception {
+ public void widevineVp9AdaptiveV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -533,7 +533,7 @@
}
@Test
- public void testWidevineVp9AdaptiveWithSeekingV24() throws Exception {
+ public void widevineVp9AdaptiveWithSeekingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -551,7 +551,7 @@
}
@Test
- public void testWidevineVp9AdaptiveWithRendererDisablingV24() throws Exception {
+ public void widevineVp9AdaptiveWithRendererDisablingV24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -572,7 +572,7 @@
// 23.976 fps.
@Test
- public void testWidevine23FpsH264FixedV23() throws Exception {
+ public void widevine23FpsH264FixedV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -590,7 +590,7 @@
// 24 fps.
@Test
- public void testWidevine24FpsH264FixedV23() throws Exception {
+ public void widevine24FpsH264FixedV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -608,7 +608,7 @@
// 29.97 fps.
@Test
- public void testWidevine29FpsH264FixedV23() throws Exception {
+ public void widevine29FpsH264FixedV23() throws Exception {
if (Util.SDK_INT < 23) {
// Pass.
return;
@@ -627,7 +627,7 @@
// Decoder info.
@Test
- public void testDecoderInfoH264() throws Exception {
+ public void decoderInfoH264() throws Exception {
MediaCodecInfo decoderInfo =
MediaCodecUtil.getDecoderInfo(
MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false);
@@ -636,7 +636,7 @@
}
@Test
- public void testDecoderInfoH265V24() throws Exception {
+ public void decoderInfoH265V24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
@@ -649,7 +649,7 @@
}
@Test
- public void testDecoderInfoVP9V24() throws Exception {
+ public void decoderInfoVP9V24() throws Exception {
if (Util.SDK_INT < 24) {
// Pass.
return;
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java
index 5deed11..5f96b96 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java
@@ -17,11 +17,13 @@
import static com.google.android.exoplayer2.C.WIDEVINE_UUID;
-import android.annotation.TargetApi;
import android.media.MediaDrm;
import android.media.UnsupportedSchemeException;
import android.net.Uri;
import android.view.Surface;
+import android.widget.FrameLayout;
+import androidx.annotation.RequiresApi;
+import androidx.test.core.app.ApplicationProvider;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
@@ -29,7 +31,6 @@
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.MediaDrmCallback;
@@ -256,8 +257,7 @@
}
@Override
- protected DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(
- final String userAgent) {
+ protected DrmSessionManager buildDrmSessionManager(final String userAgent) {
if (widevineLicenseUrl == null) {
return DrmSessionManager.getDummyDrmSessionManager();
}
@@ -265,12 +265,12 @@
MediaDrmCallback drmCallback = new HttpMediaDrmCallback(widevineLicenseUrl,
new DefaultHttpDataSourceFactory(userAgent));
FrameworkMediaDrm frameworkMediaDrm = FrameworkMediaDrm.newInstance(WIDEVINE_UUID);
- DefaultDrmSessionManager<FrameworkMediaCrypto> drmSessionManager =
- new DefaultDrmSessionManager<>(
+ DefaultDrmSessionManager drmSessionManager =
+ new DefaultDrmSessionManager(
C.WIDEVINE_UUID,
frameworkMediaDrm,
drmCallback,
- /* optionalKeyRequestParameters= */ null,
+ /* keyRequestParameters= */ null,
/* multiSession= */ false,
DefaultDrmSessionManager.INITIAL_DRM_REQUEST_RETRY_COUNT);
if (!useL1Widevine) {
@@ -299,7 +299,10 @@
@Override
protected MediaSource buildSource(
- HostActivity host, String userAgent, DrmSessionManager<?> drmSessionManager) {
+ HostActivity host,
+ String userAgent,
+ DrmSessionManager drmSessionManager,
+ FrameLayout overlayFrameLayout) {
DataSource.Factory dataSourceFactory =
this.dataSourceFactory != null
? this.dataSourceFactory
@@ -312,7 +315,7 @@
}
@Override
- protected void onTestFinished(DecoderCounters audioCounters, DecoderCounters videoCounters) {
+ protected void logMetrics(DecoderCounters audioCounters, DecoderCounters videoCounters) {
metricsLogger.logMetric(MetricsLogger.KEY_TEST_NAME, streamName);
metricsLogger.logMetric(MetricsLogger.KEY_IS_CDD_LIMITED_RETRY, isCddLimitedRetry);
metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_DROPPED_COUNT,
@@ -324,7 +327,10 @@
metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_RENDERED_COUNT,
videoCounters.renderedOutputBufferCount);
metricsLogger.close();
+ }
+ @Override
+ protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) {
if (fullPlaybackNoSeeking) {
// We shouldn't have skipped any output buffers.
DecoderCountersUtil
@@ -371,7 +377,9 @@
private DashTestTrackSelector(String tag, String audioFormatId, String[] videoFormatIds,
boolean canIncludeAdditionalVideoFormats) {
- super(new RandomTrackSelection.Factory(/* seed= */ 0));
+ super(
+ ApplicationProvider.getApplicationContext(),
+ new RandomTrackSelection.Factory(/* seed= */ 0));
this.tag = tag;
this.audioFormatId = audioFormatId;
this.videoFormatIds = videoFormatIds;
@@ -458,10 +466,10 @@
}
/**
- * Creates a new {@code MediaDrm} object. The encapsulation ensures that the tests can be
- * executed for API level < 18.
+ * Creates a new {@code MediaDrm} object. The encapsulation ensures that the tests can be executed
+ * for API level < 18.
*/
- @TargetApi(18)
+ @RequiresApi(18)
private static final class MediaDrmBuilder {
public static MediaDrm build () {
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
index efc6c01..befdc49 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
@@ -27,7 +27,6 @@
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.OfflineLicenseHelper;
import com.google.android.exoplayer2.source.dash.DashUtil;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
@@ -35,6 +34,7 @@
import com.google.android.exoplayer2.testutil.HostActivity;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
@@ -53,7 +53,7 @@
private DashTestRunner testRunner;
private DefaultHttpDataSourceFactory httpDataSourceFactory;
- private OfflineLicenseHelper<FrameworkMediaCrypto> offlineLicenseHelper;
+ private OfflineLicenseHelper offlineLicenseHelper;
private byte[] offlineLicenseKeySetId;
@Rule public ActivityTestRule<HostActivity> testRule = new ActivityTestRule<>(HostActivity.class);
@@ -75,8 +75,9 @@
String widevineLicenseUrl = DashTestData.getWidevineLicenseUrl(true, useL1Widevine);
httpDataSourceFactory = new DefaultHttpDataSourceFactory(USER_AGENT);
if (Util.SDK_INT >= 18) {
- offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance(widevineLicenseUrl,
- httpDataSourceFactory);
+ offlineLicenseHelper =
+ OfflineLicenseHelper.newWidevineInstance(
+ widevineLicenseUrl, httpDataSourceFactory, new MediaSourceEventDispatcher());
}
}
@@ -96,7 +97,7 @@
// Offline license tests
@Test
- public void testWidevineOfflineLicenseV22() throws Exception {
+ public void widevineOfflineLicenseV22() throws Exception {
if (Util.SDK_INT < 22) {
return; // Pass.
}
@@ -109,7 +110,7 @@
}
@Test
- public void testWidevineOfflineReleasedLicenseV22() throws Throwable {
+ public void widevineOfflineReleasedLicenseV22() throws Throwable {
if (Util.SDK_INT < 22) {
return; // Pass.
}
@@ -136,7 +137,7 @@
}
@Test
- public void testWidevineOfflineExpiredLicenseV22() throws Exception {
+ public void widevineOfflineExpiredLicenseV22() throws Exception {
if (Util.SDK_INT < 22) {
return; // Pass.
}
@@ -166,7 +167,7 @@
}
@Test
- public void testWidevineOfflineLicenseExpiresOnPauseV22() throws Exception {
+ public void widevineOfflineLicenseExpiresOnPauseV22() throws Exception {
if (Util.SDK_INT < 22) {
return; // Pass.
}
@@ -188,7 +189,7 @@
testRunner.setActionSchedule(schedule).run();
}
- private void downloadLicense() throws InterruptedException, DrmSessionException, IOException {
+ private void downloadLicense() throws IOException {
DataSource dataSource = httpDataSourceFactory.createDataSource();
DashManifest dashManifest = DashUtil.loadManifest(dataSource,
Uri.parse(DashTestData.WIDEVINE_H264_MANIFEST));
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
index 2704d3a..04b15f5 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
@@ -15,20 +15,18 @@
*/
package com.google.android.exoplayer2.playbacktests.gts;
-import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.os.Handler;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
@@ -53,8 +51,6 @@
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
- @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
boolean enableDecoderFallback,
Handler eventHandler,
VideoRendererEventListener eventListener,
@@ -65,8 +61,6 @@
context,
mediaCodecSelector,
allowedVideoJoiningTimeMs,
- drmSessionManager,
- playClearSamplesWithoutKeys,
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
@@ -78,6 +72,7 @@
*/
private static class DebugMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
+ private static final String TAG = "DebugMediaCodecVideoRenderer";
private static final int ARRAY_SIZE = 1000;
private final long[] timestampsList = new long[ARRAY_SIZE];
@@ -92,8 +87,6 @@
Context context,
MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
- DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
- boolean playClearSamplesWithoutKeys,
Handler eventHandler,
VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
@@ -101,14 +94,17 @@
context,
mediaCodecSelector,
allowedJoiningTimeMs,
- drmSessionManager,
- playClearSamplesWithoutKeys,
eventHandler,
eventListener,
maxDroppedFrameCountToNotify);
}
@Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
@@ -155,10 +151,11 @@
protected boolean processOutputBuffer(
long positionUs,
long elapsedRealtimeUs,
- MediaCodec codec,
+ @Nullable MediaCodec codec,
ByteBuffer buffer,
int bufferIndex,
int bufferFlags,
+ int sampleCount,
long bufferPresentationTimeUs,
boolean isDecodeOnlyBuffer,
boolean isLastBuffer,
@@ -177,6 +174,7 @@
buffer,
bufferIndex,
bufferFlags,
+ sampleCount,
bufferPresentationTimeUs,
isDecodeOnlyBuffer,
isLastBuffer,
@@ -189,10 +187,10 @@
super.renderOutputBuffer(codec, index, presentationTimeUs);
}
- @TargetApi(21)
+ @RequiresApi(21)
@Override
- protected void renderOutputBufferV21(MediaCodec codec, int index, long presentationTimeUs,
- long releaseTimeNs) {
+ protected void renderOutputBufferV21(
+ MediaCodec codec, int index, long presentationTimeUs, long releaseTimeNs) {
skipToPositionBeforeRenderingFirstFrame = false;
super.renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs);
}
diff --git a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java
index f7b376d..a29b056 100644
--- a/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java
+++ b/tree/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/EnumerateDecodersTest.java
@@ -47,7 +47,7 @@
}
@Test
- public void testEnumerateDecoders() throws Exception {
+ public void enumerateDecoders() throws Exception {
enumerateDecoders(MimeTypes.VIDEO_H263);
enumerateDecoders(MimeTypes.VIDEO_H264);
enumerateDecoders(MimeTypes.VIDEO_H265);
diff --git a/tree/settings.gradle b/tree/settings.gradle
index 39e4791..946b5b7 100644
--- a/tree/settings.gradle
+++ b/tree/settings.gradle
@@ -20,10 +20,12 @@
include modulePrefix + 'demo'
include modulePrefix + 'demo-cast'
+include modulePrefix + 'demo-gl'
include modulePrefix + 'demo-surface'
include modulePrefix + 'playbacktests'
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
+project(modulePrefix + 'demo-gl').projectDir = new File(rootDir, 'demos/gl')
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
diff --git a/tree/testdata/README.md b/tree/testdata/README.md
new file mode 100644
index 0000000..9115059
--- /dev/null
+++ b/tree/testdata/README.md
@@ -0,0 +1,4 @@
+# ExoPlayer test data #
+
+Provides sample data for ExoPlayer unit and instrumentation tests.
+
diff --git a/tree/testdata/build.gradle b/tree/testdata/build.gradle
new file mode 100644
index 0000000..372a011
--- /dev/null
+++ b/tree/testdata/build.gradle
@@ -0,0 +1,18 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+apply from: '../constants.gradle'
+apply plugin: 'com.android.library'
+
+android.compileSdkVersion project.ext.compileSdkVersion
+
diff --git a/tree/testdata/src/main/AndroidManifest.xml b/tree/testdata/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..280235f
--- /dev/null
+++ b/tree/testdata/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest package="com.google.android.exoplayer2.testdata"/>
diff --git a/tree/testdata/src/test/assets/ad-responses/midroll.xml b/tree/testdata/src/test/assets/ad-responses/midroll.xml
new file mode 100644
index 0000000..bbf216b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ad-responses/midroll.xml
@@ -0,0 +1,95 @@
+<vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">
+ <vmap:AdBreak timeOffset="start" breakType="linear" breakId="preroll">
+ <vmap:AdSource id="preroll-ad-1" allowMultipleAds="false" followRedirects="true">
+ <vmap:VASTAdData>
+ <VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast.xsd" version="3.0">
+ <Ad id="709684336">
+ <InLine>
+ <AdSystem>GDFP</AdSystem>
+ <AdTitle>Preroll</AdTitle>
+ <Description>
+ <![CDATA[ Preroll ad ]]>
+ </Description>
+ <Creatives>
+ <Creative id="57861016576" sequence="1">
+ <Linear>
+ <Duration>00:00:05</Duration>
+ <MediaFiles>
+ <MediaFile id="GDFP" delivery="progressive" width="640" height="360" type="video/mp4" bitrate="450" scalable="true" maintainAspectRatio="true">
+ <![CDATA[
+file:///android_asset/mp4/preroll-5s.mp4
+]]>
+ </MediaFile>
+ </MediaFiles>
+ </Linear>
+ </Creative>
+ </Creatives>
+ </InLine>
+ </Ad>
+ </VAST>
+ </vmap:VASTAdData>
+ </vmap:AdSource>
+ </vmap:AdBreak>
+ <vmap:AdBreak timeOffset="00:00:06.000" breakType="linear" breakId="midroll-1">
+ <vmap:AdSource id="midroll-1-ad-1" allowMultipleAds="false" followRedirects="true">
+ <vmap:VASTAdData>
+ <VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast.xsd" version="3.0">
+ <Ad id="710742616">
+ <InLine>
+ <AdSystem>GDFP</AdSystem>
+ <AdTitle>Midroll</AdTitle>
+ <Description>
+ <![CDATA[ Midroll ad ]]>
+ </Description>
+ <Creatives>
+ <Creative id="57861202456" sequence="1">
+ <Linear>
+ <Duration>00:00:05</Duration>
+ <MediaFiles>
+ <MediaFile id="GDFP" delivery="progressive" width="640" height="360" type="video/mp4" bitrate="450" scalable="true" maintainAspectRatio="true">
+ <![CDATA[
+file:///android_asset/mp4/midroll-5s.mp4
+]]>
+ </MediaFile>
+ </MediaFiles>
+ </Linear>
+ </Creative>
+ </Creatives>
+ </InLine>
+ </Ad>
+ </VAST>
+ </vmap:VASTAdData>
+ </vmap:AdSource>
+ </vmap:AdBreak>
+ <vmap:AdBreak timeOffset="end" breakType="linear" breakId="postroll">
+ <vmap:AdSource id="postroll-ad-1" allowMultipleAds="false" followRedirects="true">
+ <vmap:VASTAdData>
+ <VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast.xsd" version="3.0">
+ <Ad id="710742616">
+ <InLine>
+ <AdSystem>GDFP</AdSystem>
+ <AdTitle>Postroll</AdTitle>
+ <Description>
+ <![CDATA[ Postroll ad ]]>
+ </Description>
+ <Creatives>
+ <Creative id="57861202456" sequence="1">
+ <Linear>
+ <Duration>00:00:05</Duration>
+ <MediaFiles>
+ <MediaFile id="GDFP" delivery="progressive" width="640" height="360" type="video/mp4" bitrate="450" scalable="true" maintainAspectRatio="true">
+ <![CDATA[
+file:///android_asset/mp4/postroll-5s.mp4
+]]>
+ </MediaFile>
+ </MediaFiles>
+ </Linear>
+ </Creative>
+ </Creatives>
+ </InLine>
+ </Ad>
+ </VAST>
+ </vmap:VASTAdData>
+ </vmap:AdSource>
+ </vmap:AdBreak>
+</vmap:VMAP>
diff --git a/tree/testdata/src/test/assets/ad-responses/preroll.xml b/tree/testdata/src/test/assets/ad-responses/preroll.xml
new file mode 100644
index 0000000..3456649
--- /dev/null
+++ b/tree/testdata/src/test/assets/ad-responses/preroll.xml
@@ -0,0 +1,26 @@
+<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ version="3.0" xsi:noNamespaceSchemaLocation="vast.xsd">
+ <Ad id="697203496">
+ <InLine>
+ <AdSystem>GDFP</AdSystem>
+ <AdTitle>Preroll</AdTitle>
+ <Description>
+ <![CDATA[ Preroll ]]>
+ </Description>
+ <Creatives>
+ <Creative id="57859154776" sequence="1">
+ <Linear>
+ <Duration>00:00:05</Duration>
+ <MediaFiles>
+ <MediaFile id="GDFP" delivery="progressive" width="640" height="360" type="video/mp4" bitrate="450" scalable="true" maintainAspectRatio="true">
+ <![CDATA[
+file:///android_asset/mp4/preroll-5s.mp4
+]]>
+ </MediaFile>
+ </MediaFiles>
+ </Linear>
+ </Creative>
+ </Creatives>
+ </InLine>
+ </Ad>
+</VAST>
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb.amr b/tree/testdata/src/test/assets/amr/sample_nb.amr
similarity index 100%
rename from tree/library/extractor/src/test/assets/amr/sample_nb.amr
rename to tree/testdata/src/test/assets/amr/sample_nb.amr
Binary files differ
diff --git a/tree/testdata/src/test/assets/amr/sample_nb.amr.0.dump b/tree/testdata/src/test/assets/amr/sample_nb.amr.0.dump
new file mode 100644
index 0000000..8430964
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb.amr.0.dump
@@ -0,0 +1,886 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2834
+ sample count = 218
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 13, hash 371B046C
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 13, hash CE30BF5B
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 13, hash 19A59975
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 13, hash 4879773C
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 13, hash E8F83019
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 13, hash D265CDC9
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 13, hash 91653DAA
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 13, hash C79456F6
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 13, hash CDDC4422
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 13, hash D9ED3AF1
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 13, hash BAB75A33
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 13, hash 2221B4FF
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 13, hash 96400A0B
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 13, hash 582E6FB
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 13, hash C4E878E5
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 13, hash C849A1BD
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 13, hash CFA8A9ED
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 13, hash 70CA4907
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 13, hash B47D4454
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 13, hash 282998C1
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 13, hash 3F3F7A65
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 13, hash CC2EAB58
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 13, hash 279EF712
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 13, hash AA2F4B29
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 13, hash F6F658C4
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 13, hash D7DEBD17
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 13, hash 6DAB9A17
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 13, hash 6ECE1571
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 13, hash B3D0507F
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 13, hash 21E356B9
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 13, hash 410EA12
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 13, hash 533895A8
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 13, hash C61B3E5A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 13, hash 982170E6
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 13, hash 7A0468C5
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 13, hash 9C85EAA7
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 13, hash B6B341B6
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 13, hash 6937532E
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 13, hash 8CF2A3A0
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 13, hash D2682AC6
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 13, hash BBC5710F
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 13, hash 59080B6C
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 13, hash E4118291
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 13, hash A1E5B296
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 13, hash D7B8F95B
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 13, hash CC839BE1
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 13, hash D459DFCE
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 13, hash D6AD19EC
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 13, hash D05E373D
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 13, hash 6A4460C7
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 13, hash C9A0D93F
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 13, hash 3FA819E7
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 13, hash 1D3CBDFC
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 13, hash 8BBBB403
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 13, hash 21B4A0F9
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 13, hash C0F921D1
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 13, hash 5D812AAB
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 13, hash 50C9F3F8
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 13, hash 5C2BB5D1
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 13, hash 6BF9BEA5
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 13, hash 2738C1E6
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 13, hash 5FC288A6
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 13, hash 7E8E442A
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 13, hash AEAA2BBA
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 13, hash 4E2ACD2F
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 13, hash D6C90ACF
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 13, hash 6FD8A944
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 13, hash A835BBF9
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 13, hash F7713830
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 13, hash 3AA966E5
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 13, hash F939E829
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 13, hash 7676DE49
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 13, hash 93BB890A
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 13, hash B57DBEC8
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 13, hash 66B0A5B6
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 13, hash D733E0D
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 13, hash 80941726
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 13, hash 556ED633
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 13, hash C5EDF4E1
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 13, hash 6B287445
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 13, hash DC97C4A7
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 13, hash DA8CBDF4
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 13, hash 6F60FF77
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 13, hash 3EB22B96
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 13, hash B3C31AF5
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 13, hash 1854AA92
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 13, hash 6488264B
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 13, hash 4CC8C5C1
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 13, hash 19CC7523
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 13, hash 9BE7B928
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 13, hash 47EC7CFD
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 13, hash EC940120
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 13, hash 73BDA6D0
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 13, hash FACB3314
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 13, hash EC61D13B
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 13, hash B28C7B6C
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 13, hash B1A4CECD
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 13, hash 56D41BA6
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 13, hash 90499F4
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 13, hash 65D9A9D3
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 13, hash D9004CC
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 13, hash 4139C6ED
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 13, hash C4F8097C
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 13, hash 94D424FA
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 13, hash C2C6F5FD
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 13, hash 15719008
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 13, hash 4F64F524
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 13, hash F9E01C1E
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 13, hash 74C4EE74
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 13, hash 7EE7553D
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 13, hash 62DE6539
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 13, hash 7F5EC222
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 13, hash 644067F
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 13, hash CDF6C9DC
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 13, hash 8B5DBC80
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 13, hash AD4BBA03
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 13, hash 7A76340
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 13, hash 3610F5B0
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 13, hash 430BC60B
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 13, hash 99CF1CA6
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 13, hash 1331C70B
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 13, hash BD76E69D
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 13, hash 5DA652AC
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 13, hash 3B7BF6CE
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 13, hash ABBFD143
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 13, hash E9447166
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 13, hash EC40068C
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 13, hash A2869400
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 13, hash C7E0746B
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 13, hash 60601BB1
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 13, hash 975AAE9B
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 13, hash 8BBC0EB2
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 13, hash 57FB39E5
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 13, hash 4CDCEEDB
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 13, hash EA16E256
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 13, hash 287E7D9E
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 13, hash 55AB8FB9
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 13, hash 129890EF
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 13, hash 90834F57
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 13, hash 5B3228E0
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 13, hash DD19E175
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 13, hash EE7EA342
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 13, hash DB3AF473
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 13, hash 25AEC43F
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 13, hash EE9BF97F
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 169:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 170:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 171:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 172:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 173:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 174:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 175:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 176:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 177:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 178:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 179:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 180:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 181:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 182:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 183:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 184:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 185:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 186:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 187:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 188:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 189:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 190:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 191:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 192:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 193:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 194:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 195:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 196:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 197:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 198:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 199:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 200:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 201:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 202:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 203:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 204:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 205:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 206:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 207:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 208:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 209:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 210:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 211:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 212:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 213:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 214:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 215:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 216:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 217:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_nb.amr.unknown_length.dump b/tree/testdata/src/test/assets/amr/sample_nb.amr.unknown_length.dump
new file mode 100644
index 0000000..8430964
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb.amr.unknown_length.dump
@@ -0,0 +1,886 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2834
+ sample count = 218
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 13, hash 371B046C
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 13, hash CE30BF5B
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 13, hash 19A59975
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 13, hash 4879773C
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 13, hash E8F83019
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 13, hash D265CDC9
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 13, hash 91653DAA
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 13, hash C79456F6
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 13, hash CDDC4422
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 13, hash D9ED3AF1
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 13, hash BAB75A33
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 13, hash 2221B4FF
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 13, hash 96400A0B
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 13, hash 582E6FB
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 13, hash C4E878E5
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 13, hash C849A1BD
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 13, hash CFA8A9ED
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 13, hash 70CA4907
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 13, hash B47D4454
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 13, hash 282998C1
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 13, hash 3F3F7A65
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 13, hash CC2EAB58
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 13, hash 279EF712
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 13, hash AA2F4B29
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 13, hash F6F658C4
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 13, hash D7DEBD17
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 13, hash 6DAB9A17
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 13, hash 6ECE1571
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 13, hash B3D0507F
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 13, hash 21E356B9
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 13, hash 410EA12
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 13, hash 533895A8
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 13, hash C61B3E5A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 13, hash 982170E6
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 13, hash 7A0468C5
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 13, hash 9C85EAA7
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 13, hash B6B341B6
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 13, hash 6937532E
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 13, hash 8CF2A3A0
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 13, hash D2682AC6
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 13, hash BBC5710F
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 13, hash 59080B6C
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 13, hash E4118291
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 13, hash A1E5B296
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 13, hash D7B8F95B
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 13, hash CC839BE1
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 13, hash D459DFCE
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 13, hash D6AD19EC
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 13, hash D05E373D
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 13, hash 6A4460C7
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 13, hash C9A0D93F
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 13, hash 3FA819E7
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 13, hash 1D3CBDFC
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 13, hash 8BBBB403
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 13, hash 21B4A0F9
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 13, hash C0F921D1
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 13, hash 5D812AAB
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 13, hash 50C9F3F8
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 13, hash 5C2BB5D1
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 13, hash 6BF9BEA5
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 13, hash 2738C1E6
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 13, hash 5FC288A6
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 13, hash 7E8E442A
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 13, hash AEAA2BBA
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 13, hash 4E2ACD2F
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 13, hash D6C90ACF
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 13, hash 6FD8A944
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 13, hash A835BBF9
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 13, hash F7713830
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 13, hash 3AA966E5
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 13, hash F939E829
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 13, hash 7676DE49
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 13, hash 93BB890A
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 13, hash B57DBEC8
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 13, hash 66B0A5B6
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 13, hash D733E0D
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 13, hash 80941726
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 13, hash 556ED633
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 13, hash C5EDF4E1
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 13, hash 6B287445
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 13, hash DC97C4A7
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 13, hash DA8CBDF4
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 13, hash 6F60FF77
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 13, hash 3EB22B96
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 13, hash B3C31AF5
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 13, hash 1854AA92
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 13, hash 6488264B
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 13, hash 4CC8C5C1
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 13, hash 19CC7523
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 13, hash 9BE7B928
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 13, hash 47EC7CFD
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 13, hash EC940120
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 13, hash 73BDA6D0
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 13, hash FACB3314
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 13, hash EC61D13B
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 13, hash B28C7B6C
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 13, hash B1A4CECD
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 13, hash 56D41BA6
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 13, hash 90499F4
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 13, hash 65D9A9D3
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 13, hash D9004CC
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 13, hash 4139C6ED
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 13, hash C4F8097C
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 13, hash 94D424FA
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 13, hash C2C6F5FD
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 13, hash 15719008
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 13, hash 4F64F524
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 13, hash F9E01C1E
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 13, hash 74C4EE74
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 13, hash 7EE7553D
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 13, hash 62DE6539
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 13, hash 7F5EC222
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 13, hash 644067F
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 13, hash CDF6C9DC
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 13, hash 8B5DBC80
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 13, hash AD4BBA03
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 13, hash 7A76340
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 13, hash 3610F5B0
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 13, hash 430BC60B
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 13, hash 99CF1CA6
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 13, hash 1331C70B
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 13, hash BD76E69D
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 13, hash 5DA652AC
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 13, hash 3B7BF6CE
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 13, hash ABBFD143
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 13, hash E9447166
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 13, hash EC40068C
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 13, hash A2869400
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 13, hash C7E0746B
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 13, hash 60601BB1
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 13, hash 975AAE9B
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 13, hash 8BBC0EB2
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 13, hash 57FB39E5
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 13, hash 4CDCEEDB
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 13, hash EA16E256
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 13, hash 287E7D9E
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 13, hash 55AB8FB9
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 13, hash 129890EF
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 13, hash 90834F57
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 13, hash 5B3228E0
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 13, hash DD19E175
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 13, hash EE7EA342
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 13, hash DB3AF473
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 13, hash 25AEC43F
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 13, hash EE9BF97F
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 169:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 170:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 171:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 172:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 173:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 174:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 175:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 176:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 177:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 178:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 179:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 180:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 181:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 182:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 183:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 184:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 185:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 186:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 187:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 188:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 189:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 190:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 191:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 192:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 193:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 194:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 195:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 196:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 197:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 198:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 199:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 200:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 201:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 202:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 203:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 204:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 205:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 206:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 207:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 208:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 209:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 210:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 211:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 212:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 213:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 214:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 215:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 216:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 217:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr
similarity index 100%
rename from tree/library/extractor/src/test/assets/amr/sample_nb_cbr.amr
rename to tree/testdata/src/test/assets/amr/sample_nb_cbr.amr
Binary files differ
diff --git a/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.0.dump b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.0.dump
new file mode 100644
index 0000000..ceef911
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.0.dump
@@ -0,0 +1,889 @@
+seekMap:
+ isSeekable = true
+ duration = 4360000
+ getPosition(0) = [[timeUs=0, position=6]]
+ getPosition(1) = [[timeUs=0, position=6], [timeUs=20000, position=19]]
+ getPosition(2180000) = [[timeUs=2180000, position=1423]]
+ getPosition(4360000) = [[timeUs=4340000, position=2827]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2834
+ sample count = 218
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 13, hash 371B046C
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 13, hash CE30BF5B
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 13, hash 19A59975
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 13, hash 4879773C
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 13, hash E8F83019
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 13, hash D265CDC9
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 13, hash 91653DAA
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 13, hash C79456F6
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 13, hash CDDC4422
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 13, hash D9ED3AF1
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 13, hash BAB75A33
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 13, hash 2221B4FF
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 13, hash 96400A0B
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 13, hash 582E6FB
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 13, hash C4E878E5
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 13, hash C849A1BD
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 13, hash CFA8A9ED
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 13, hash 70CA4907
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 13, hash B47D4454
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 13, hash 282998C1
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 13, hash 3F3F7A65
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 13, hash CC2EAB58
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 13, hash 279EF712
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 13, hash AA2F4B29
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 13, hash F6F658C4
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 13, hash D7DEBD17
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 13, hash 6DAB9A17
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 13, hash 6ECE1571
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 13, hash B3D0507F
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 13, hash 21E356B9
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 13, hash 410EA12
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 13, hash 533895A8
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 13, hash C61B3E5A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 13, hash 982170E6
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 13, hash 7A0468C5
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 13, hash 9C85EAA7
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 13, hash B6B341B6
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 13, hash 6937532E
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 13, hash 8CF2A3A0
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 13, hash D2682AC6
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 13, hash BBC5710F
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 13, hash 59080B6C
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 13, hash E4118291
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 13, hash A1E5B296
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 13, hash D7B8F95B
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 13, hash CC839BE1
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 13, hash D459DFCE
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 13, hash D6AD19EC
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 13, hash D05E373D
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 13, hash 6A4460C7
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 13, hash C9A0D93F
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 13, hash 3FA819E7
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 13, hash 1D3CBDFC
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 13, hash 8BBBB403
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 13, hash 21B4A0F9
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 13, hash C0F921D1
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 13, hash 5D812AAB
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 13, hash 50C9F3F8
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 13, hash 5C2BB5D1
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 13, hash 6BF9BEA5
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 13, hash 2738C1E6
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 13, hash 5FC288A6
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 13, hash 7E8E442A
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 13, hash AEAA2BBA
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 13, hash 4E2ACD2F
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 13, hash D6C90ACF
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 13, hash 6FD8A944
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 13, hash A835BBF9
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 13, hash F7713830
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 13, hash 3AA966E5
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 13, hash F939E829
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 13, hash 7676DE49
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 13, hash 93BB890A
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 13, hash B57DBEC8
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 13, hash 66B0A5B6
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 13, hash D733E0D
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 13, hash 80941726
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 13, hash 556ED633
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 13, hash C5EDF4E1
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 13, hash 6B287445
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 13, hash DC97C4A7
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 13, hash DA8CBDF4
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 13, hash 6F60FF77
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 13, hash 3EB22B96
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 13, hash B3C31AF5
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 13, hash 1854AA92
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 13, hash 6488264B
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 13, hash 4CC8C5C1
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 13, hash 19CC7523
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 13, hash 9BE7B928
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 13, hash 47EC7CFD
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 13, hash EC940120
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 13, hash 73BDA6D0
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 13, hash FACB3314
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 13, hash EC61D13B
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 13, hash B28C7B6C
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 13, hash B1A4CECD
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 13, hash 56D41BA6
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 13, hash 90499F4
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 13, hash 65D9A9D3
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 13, hash D9004CC
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 13, hash 4139C6ED
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 13, hash C4F8097C
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 13, hash 94D424FA
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 13, hash C2C6F5FD
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 13, hash 15719008
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 13, hash 4F64F524
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 13, hash F9E01C1E
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 13, hash 74C4EE74
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 13, hash 7EE7553D
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 13, hash 62DE6539
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 13, hash 7F5EC222
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 13, hash 644067F
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 13, hash CDF6C9DC
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 13, hash 8B5DBC80
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 13, hash AD4BBA03
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 13, hash 7A76340
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 13, hash 3610F5B0
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 13, hash 430BC60B
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 13, hash 99CF1CA6
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 13, hash 1331C70B
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 13, hash BD76E69D
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 13, hash 5DA652AC
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 13, hash 3B7BF6CE
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 13, hash ABBFD143
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 13, hash E9447166
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 13, hash EC40068C
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 13, hash A2869400
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 13, hash C7E0746B
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 13, hash 60601BB1
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 13, hash 975AAE9B
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 13, hash 8BBC0EB2
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 13, hash 57FB39E5
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 13, hash 4CDCEEDB
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 13, hash EA16E256
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 13, hash 287E7D9E
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 13, hash 55AB8FB9
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 13, hash 129890EF
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 13, hash 90834F57
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 13, hash 5B3228E0
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 13, hash DD19E175
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 13, hash EE7EA342
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 13, hash DB3AF473
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 13, hash 25AEC43F
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 13, hash EE9BF97F
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 169:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 170:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 171:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 172:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 173:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 174:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 175:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 176:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 177:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 178:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 179:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 180:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 181:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 182:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 183:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 184:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 185:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 186:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 187:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 188:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 189:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 190:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 191:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 192:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 193:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 194:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 195:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 196:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 197:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 198:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 199:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 200:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 201:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 202:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 203:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 204:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 205:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 206:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 207:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 208:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 209:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 210:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 211:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 212:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 213:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 214:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 215:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 216:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 217:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.1.dump b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.1.dump
new file mode 100644
index 0000000..a4cb365
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.1.dump
@@ -0,0 +1,601 @@
+seekMap:
+ isSeekable = true
+ duration = 4360000
+ getPosition(0) = [[timeUs=0, position=6]]
+ getPosition(1) = [[timeUs=0, position=6], [timeUs=20000, position=19]]
+ getPosition(2180000) = [[timeUs=2180000, position=1423]]
+ getPosition(4360000) = [[timeUs=4340000, position=2827]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 1898
+ sample count = 146
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 1440000
+ flags = 1
+ data = length 13, hash 93BB890A
+ sample 1:
+ time = 1460000
+ flags = 1
+ data = length 13, hash B57DBEC8
+ sample 2:
+ time = 1480000
+ flags = 1
+ data = length 13, hash 66B0A5B6
+ sample 3:
+ time = 1500000
+ flags = 1
+ data = length 13, hash D733E0D
+ sample 4:
+ time = 1520000
+ flags = 1
+ data = length 13, hash 80941726
+ sample 5:
+ time = 1540000
+ flags = 1
+ data = length 13, hash 556ED633
+ sample 6:
+ time = 1560000
+ flags = 1
+ data = length 13, hash C5EDF4E1
+ sample 7:
+ time = 1580000
+ flags = 1
+ data = length 13, hash 6B287445
+ sample 8:
+ time = 1600000
+ flags = 1
+ data = length 13, hash DC97C4A7
+ sample 9:
+ time = 1620000
+ flags = 1
+ data = length 13, hash DA8CBDF4
+ sample 10:
+ time = 1640000
+ flags = 1
+ data = length 13, hash 6F60FF77
+ sample 11:
+ time = 1660000
+ flags = 1
+ data = length 13, hash 3EB22B96
+ sample 12:
+ time = 1680000
+ flags = 1
+ data = length 13, hash B3C31AF5
+ sample 13:
+ time = 1700000
+ flags = 1
+ data = length 13, hash 1854AA92
+ sample 14:
+ time = 1720000
+ flags = 1
+ data = length 13, hash 6488264B
+ sample 15:
+ time = 1740000
+ flags = 1
+ data = length 13, hash 4CC8C5C1
+ sample 16:
+ time = 1760000
+ flags = 1
+ data = length 13, hash 19CC7523
+ sample 17:
+ time = 1780000
+ flags = 1
+ data = length 13, hash 9BE7B928
+ sample 18:
+ time = 1800000
+ flags = 1
+ data = length 13, hash 47EC7CFD
+ sample 19:
+ time = 1820000
+ flags = 1
+ data = length 13, hash EC940120
+ sample 20:
+ time = 1840000
+ flags = 1
+ data = length 13, hash 73BDA6D0
+ sample 21:
+ time = 1860000
+ flags = 1
+ data = length 13, hash FACB3314
+ sample 22:
+ time = 1880000
+ flags = 1
+ data = length 13, hash EC61D13B
+ sample 23:
+ time = 1900000
+ flags = 1
+ data = length 13, hash B28C7B6C
+ sample 24:
+ time = 1920000
+ flags = 1
+ data = length 13, hash B1A4CECD
+ sample 25:
+ time = 1940000
+ flags = 1
+ data = length 13, hash 56D41BA6
+ sample 26:
+ time = 1960000
+ flags = 1
+ data = length 13, hash 90499F4
+ sample 27:
+ time = 1980000
+ flags = 1
+ data = length 13, hash 65D9A9D3
+ sample 28:
+ time = 2000000
+ flags = 1
+ data = length 13, hash D9004CC
+ sample 29:
+ time = 2020000
+ flags = 1
+ data = length 13, hash 4139C6ED
+ sample 30:
+ time = 2040000
+ flags = 1
+ data = length 13, hash C4F8097C
+ sample 31:
+ time = 2060000
+ flags = 1
+ data = length 13, hash 94D424FA
+ sample 32:
+ time = 2080000
+ flags = 1
+ data = length 13, hash C2C6F5FD
+ sample 33:
+ time = 2100000
+ flags = 1
+ data = length 13, hash 15719008
+ sample 34:
+ time = 2120000
+ flags = 1
+ data = length 13, hash 4F64F524
+ sample 35:
+ time = 2140000
+ flags = 1
+ data = length 13, hash F9E01C1E
+ sample 36:
+ time = 2160000
+ flags = 1
+ data = length 13, hash 74C4EE74
+ sample 37:
+ time = 2180000
+ flags = 1
+ data = length 13, hash 7EE7553D
+ sample 38:
+ time = 2200000
+ flags = 1
+ data = length 13, hash 62DE6539
+ sample 39:
+ time = 2220000
+ flags = 1
+ data = length 13, hash 7F5EC222
+ sample 40:
+ time = 2240000
+ flags = 1
+ data = length 13, hash 644067F
+ sample 41:
+ time = 2260000
+ flags = 1
+ data = length 13, hash CDF6C9DC
+ sample 42:
+ time = 2280000
+ flags = 1
+ data = length 13, hash 8B5DBC80
+ sample 43:
+ time = 2300000
+ flags = 1
+ data = length 13, hash AD4BBA03
+ sample 44:
+ time = 2320000
+ flags = 1
+ data = length 13, hash 7A76340
+ sample 45:
+ time = 2340000
+ flags = 1
+ data = length 13, hash 3610F5B0
+ sample 46:
+ time = 2360000
+ flags = 1
+ data = length 13, hash 430BC60B
+ sample 47:
+ time = 2380000
+ flags = 1
+ data = length 13, hash 99CF1CA6
+ sample 48:
+ time = 2400000
+ flags = 1
+ data = length 13, hash 1331C70B
+ sample 49:
+ time = 2420000
+ flags = 1
+ data = length 13, hash BD76E69D
+ sample 50:
+ time = 2440000
+ flags = 1
+ data = length 13, hash 5DA652AC
+ sample 51:
+ time = 2460000
+ flags = 1
+ data = length 13, hash 3B7BF6CE
+ sample 52:
+ time = 2480000
+ flags = 1
+ data = length 13, hash ABBFD143
+ sample 53:
+ time = 2500000
+ flags = 1
+ data = length 13, hash E9447166
+ sample 54:
+ time = 2520000
+ flags = 1
+ data = length 13, hash EC40068C
+ sample 55:
+ time = 2540000
+ flags = 1
+ data = length 13, hash A2869400
+ sample 56:
+ time = 2560000
+ flags = 1
+ data = length 13, hash C7E0746B
+ sample 57:
+ time = 2580000
+ flags = 1
+ data = length 13, hash 60601BB1
+ sample 58:
+ time = 2600000
+ flags = 1
+ data = length 13, hash 975AAE9B
+ sample 59:
+ time = 2620000
+ flags = 1
+ data = length 13, hash 8BBC0EB2
+ sample 60:
+ time = 2640000
+ flags = 1
+ data = length 13, hash 57FB39E5
+ sample 61:
+ time = 2660000
+ flags = 1
+ data = length 13, hash 4CDCEEDB
+ sample 62:
+ time = 2680000
+ flags = 1
+ data = length 13, hash EA16E256
+ sample 63:
+ time = 2700000
+ flags = 1
+ data = length 13, hash 287E7D9E
+ sample 64:
+ time = 2720000
+ flags = 1
+ data = length 13, hash 55AB8FB9
+ sample 65:
+ time = 2740000
+ flags = 1
+ data = length 13, hash 129890EF
+ sample 66:
+ time = 2760000
+ flags = 1
+ data = length 13, hash 90834F57
+ sample 67:
+ time = 2780000
+ flags = 1
+ data = length 13, hash 5B3228E0
+ sample 68:
+ time = 2800000
+ flags = 1
+ data = length 13, hash DD19E175
+ sample 69:
+ time = 2820000
+ flags = 1
+ data = length 13, hash EE7EA342
+ sample 70:
+ time = 2840000
+ flags = 1
+ data = length 13, hash DB3AF473
+ sample 71:
+ time = 2860000
+ flags = 1
+ data = length 13, hash 25AEC43F
+ sample 72:
+ time = 2880000
+ flags = 1
+ data = length 13, hash EE9BF97F
+ sample 73:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 74:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 75:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 76:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 77:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 78:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 79:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 80:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 81:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 82:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 83:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 84:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 85:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 86:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 87:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 88:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 89:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 90:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 91:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 92:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 93:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 94:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 95:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 96:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 97:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 98:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 99:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 100:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 101:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 102:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 103:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 104:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 105:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 106:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 107:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 108:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 109:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 110:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 111:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 112:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 113:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 114:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 115:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 116:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 117:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 118:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 119:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 120:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 121:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 122:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 123:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 124:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 125:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 126:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 127:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 128:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 129:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 130:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 131:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 132:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 133:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 134:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 135:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 136:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 137:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 138:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 139:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 140:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 141:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 142:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 143:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 144:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 145:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.2.dump b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.2.dump
new file mode 100644
index 0000000..57fb75c
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.2.dump
@@ -0,0 +1,309 @@
+seekMap:
+ isSeekable = true
+ duration = 4360000
+ getPosition(0) = [[timeUs=0, position=6]]
+ getPosition(1) = [[timeUs=0, position=6], [timeUs=20000, position=19]]
+ getPosition(2180000) = [[timeUs=2180000, position=1423]]
+ getPosition(4360000) = [[timeUs=4340000, position=2827]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 949
+ sample count = 73
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 1:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 2:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 3:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 4:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 5:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 6:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 7:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 8:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 9:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 10:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 11:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 12:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 13:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 14:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 15:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 16:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 17:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 18:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 19:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 20:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 21:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 22:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 23:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 24:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 25:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 26:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 27:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 28:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 29:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 30:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 31:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 32:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 33:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 34:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 35:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 36:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 37:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 38:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 39:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 40:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 41:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 42:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 43:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 44:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 45:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 46:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 47:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 48:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 49:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 50:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 51:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 52:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 53:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 54:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 55:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 56:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 57:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 58:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 59:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 60:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 61:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 62:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 63:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 64:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 65:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 66:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 67:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 68:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 69:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 70:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 71:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 72:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.3.dump b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.3.dump
new file mode 100644
index 0000000..a77bd61
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.3.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 4360000
+ getPosition(0) = [[timeUs=0, position=6]]
+ getPosition(1) = [[timeUs=0, position=6], [timeUs=20000, position=19]]
+ getPosition(2180000) = [[timeUs=2180000, position=1423]]
+ getPosition(4360000) = [[timeUs=4340000, position=2827]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.unknown_length.dump b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.unknown_length.dump
new file mode 100644
index 0000000..8430964
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_nb_cbr.amr.unknown_length.dump
@@ -0,0 +1,886 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2834
+ sample count = 218
+ format 0:
+ sampleMimeType = audio/3gpp
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 8000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 13, hash 371B046C
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 13, hash CE30BF5B
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 13, hash 19A59975
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 13, hash 4879773C
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 13, hash E8F83019
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 13, hash D265CDC9
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 13, hash 91653DAA
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 13, hash C79456F6
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 13, hash CDDC4422
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 13, hash D9ED3AF1
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 13, hash BAB75A33
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 13, hash 2221B4FF
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 13, hash 96400A0B
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 13, hash 582E6FB
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 13, hash C4E878E5
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 13, hash C849A1BD
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 13, hash CFA8A9ED
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 13, hash 70CA4907
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 13, hash B47D4454
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 13, hash 282998C1
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 13, hash 3F3F7A65
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 13, hash CC2EAB58
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 13, hash 279EF712
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 13, hash AA2F4B29
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 13, hash F6F658C4
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 13, hash D7DEBD17
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 13, hash 6DAB9A17
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 13, hash 6ECE1571
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 13, hash B3D0507F
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 13, hash 21E356B9
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 13, hash 410EA12
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 13, hash 533895A8
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 13, hash C61B3E5A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 13, hash 982170E6
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 13, hash 7A0468C5
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 13, hash 9C85EAA7
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 13, hash B6B341B6
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 13, hash 6937532E
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 13, hash 8CF2A3A0
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 13, hash D2682AC6
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 13, hash BBC5710F
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 13, hash 59080B6C
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 13, hash E4118291
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 13, hash A1E5B296
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 13, hash D7B8F95B
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 13, hash CC839BE1
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 13, hash D459DFCE
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 13, hash D6AD19EC
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 13, hash D05E373D
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 13, hash 6A4460C7
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 13, hash C9A0D93F
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 13, hash 3FA819E7
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 13, hash 1D3CBDFC
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 13, hash 8BBBB403
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 13, hash 21B4A0F9
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 13, hash C0F921D1
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 13, hash 5D812AAB
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 13, hash 50C9F3F8
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 13, hash 5C2BB5D1
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 13, hash 6BF9BEA5
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 13, hash 2738C1E6
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 13, hash 5FC288A6
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 13, hash 7E8E442A
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 13, hash AEAA2BBA
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 13, hash 4E2ACD2F
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 13, hash D6C90ACF
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 13, hash 6FD8A944
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 13, hash A835BBF9
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 13, hash F7713830
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 13, hash 3AA966E5
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 13, hash F939E829
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 13, hash 7676DE49
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 13, hash 93BB890A
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 13, hash B57DBEC8
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 13, hash 66B0A5B6
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 13, hash D733E0D
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 13, hash 80941726
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 13, hash 556ED633
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 13, hash C5EDF4E1
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 13, hash 6B287445
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 13, hash DC97C4A7
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 13, hash DA8CBDF4
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 13, hash 6F60FF77
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 13, hash 3EB22B96
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 13, hash B3C31AF5
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 13, hash 1854AA92
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 13, hash 6488264B
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 13, hash 4CC8C5C1
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 13, hash 19CC7523
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 13, hash 9BE7B928
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 13, hash 47EC7CFD
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 13, hash EC940120
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 13, hash 73BDA6D0
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 13, hash FACB3314
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 13, hash EC61D13B
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 13, hash B28C7B6C
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 13, hash B1A4CECD
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 13, hash 56D41BA6
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 13, hash 90499F4
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 13, hash 65D9A9D3
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 13, hash D9004CC
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 13, hash 4139C6ED
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 13, hash C4F8097C
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 13, hash 94D424FA
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 13, hash C2C6F5FD
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 13, hash 15719008
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 13, hash 4F64F524
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 13, hash F9E01C1E
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 13, hash 74C4EE74
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 13, hash 7EE7553D
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 13, hash 62DE6539
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 13, hash 7F5EC222
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 13, hash 644067F
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 13, hash CDF6C9DC
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 13, hash 8B5DBC80
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 13, hash AD4BBA03
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 13, hash 7A76340
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 13, hash 3610F5B0
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 13, hash 430BC60B
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 13, hash 99CF1CA6
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 13, hash 1331C70B
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 13, hash BD76E69D
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 13, hash 5DA652AC
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 13, hash 3B7BF6CE
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 13, hash ABBFD143
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 13, hash E9447166
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 13, hash EC40068C
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 13, hash A2869400
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 13, hash C7E0746B
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 13, hash 60601BB1
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 13, hash 975AAE9B
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 13, hash 8BBC0EB2
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 13, hash 57FB39E5
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 13, hash 4CDCEEDB
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 13, hash EA16E256
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 13, hash 287E7D9E
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 13, hash 55AB8FB9
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 13, hash 129890EF
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 13, hash 90834F57
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 13, hash 5B3228E0
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 13, hash DD19E175
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 13, hash EE7EA342
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 13, hash DB3AF473
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 13, hash 25AEC43F
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 13, hash EE9BF97F
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 13, hash FFFBE047
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 13, hash BEACFCB0
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 13, hash AEB5096C
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 13, hash B0D381B
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 13, hash 3D9D5122
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 13, hash 6C1DDB95
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 13, hash ADACADCF
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 13, hash 159E321E
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 13, hash B1466264
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 13, hash 4DDF7223
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 13, hash C9BDB82A
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 13, hash A49B2D9D
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 13, hash D645E7E5
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 13, hash 1C4232DC
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 13, hash 83078219
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 13, hash D6D8B072
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 13, hash 975DB40
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 13, hash A15FDD05
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 13, hash 4B839E41
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 13, hash 7418F499
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 13, hash 7A4945E4
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 13, hash 6249558C
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 13, hash BD4C5BE3
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 13, hash BAB30F1D
+ sample 169:
+ time = 3380000
+ flags = 1
+ data = length 13, hash 1E1C7012
+ sample 170:
+ time = 3400000
+ flags = 1
+ data = length 13, hash 9A3F8A89
+ sample 171:
+ time = 3420000
+ flags = 1
+ data = length 13, hash 20BE6D7B
+ sample 172:
+ time = 3440000
+ flags = 1
+ data = length 13, hash CAA0591D
+ sample 173:
+ time = 3460000
+ flags = 1
+ data = length 13, hash 6D554D17
+ sample 174:
+ time = 3480000
+ flags = 1
+ data = length 13, hash D97C3B31
+ sample 175:
+ time = 3500000
+ flags = 1
+ data = length 13, hash 75BC5C3
+ sample 176:
+ time = 3520000
+ flags = 1
+ data = length 13, hash 7BA1784B
+ sample 177:
+ time = 3540000
+ flags = 1
+ data = length 13, hash 1D175D92
+ sample 178:
+ time = 3560000
+ flags = 1
+ data = length 13, hash ADCA60FD
+ sample 179:
+ time = 3580000
+ flags = 1
+ data = length 13, hash 37018693
+ sample 180:
+ time = 3600000
+ flags = 1
+ data = length 13, hash 4553606F
+ sample 181:
+ time = 3620000
+ flags = 1
+ data = length 13, hash CF434565
+ sample 182:
+ time = 3640000
+ flags = 1
+ data = length 13, hash D264D757
+ sample 183:
+ time = 3660000
+ flags = 1
+ data = length 13, hash 4FB493EF
+ sample 184:
+ time = 3680000
+ flags = 1
+ data = length 13, hash 919F53A
+ sample 185:
+ time = 3700000
+ flags = 1
+ data = length 13, hash C22B009B
+ sample 186:
+ time = 3720000
+ flags = 1
+ data = length 13, hash 5981470
+ sample 187:
+ time = 3740000
+ flags = 1
+ data = length 13, hash A5D3937C
+ sample 188:
+ time = 3760000
+ flags = 1
+ data = length 13, hash A2504429
+ sample 189:
+ time = 3780000
+ flags = 1
+ data = length 13, hash AD1B70BE
+ sample 190:
+ time = 3800000
+ flags = 1
+ data = length 13, hash 2E39ED5E
+ sample 191:
+ time = 3820000
+ flags = 1
+ data = length 13, hash 13A8BE8E
+ sample 192:
+ time = 3840000
+ flags = 1
+ data = length 13, hash 1ACD740B
+ sample 193:
+ time = 3860000
+ flags = 1
+ data = length 13, hash 80F38B3
+ sample 194:
+ time = 3880000
+ flags = 1
+ data = length 13, hash DA9DA79F
+ sample 195:
+ time = 3900000
+ flags = 1
+ data = length 13, hash 21B95B7E
+ sample 196:
+ time = 3920000
+ flags = 1
+ data = length 13, hash CD22497B
+ sample 197:
+ time = 3940000
+ flags = 1
+ data = length 13, hash 718BB35D
+ sample 198:
+ time = 3960000
+ flags = 1
+ data = length 13, hash 69ABA6AD
+ sample 199:
+ time = 3980000
+ flags = 1
+ data = length 13, hash BAE19549
+ sample 200:
+ time = 4000000
+ flags = 1
+ data = length 13, hash 2A792FB3
+ sample 201:
+ time = 4020000
+ flags = 1
+ data = length 13, hash 71FCD8
+ sample 202:
+ time = 4040000
+ flags = 1
+ data = length 13, hash 44D2B5B3
+ sample 203:
+ time = 4060000
+ flags = 1
+ data = length 13, hash 1E87B11B
+ sample 204:
+ time = 4080000
+ flags = 1
+ data = length 13, hash 78CD2C11
+ sample 205:
+ time = 4100000
+ flags = 1
+ data = length 13, hash 9F198DF0
+ sample 206:
+ time = 4120000
+ flags = 1
+ data = length 13, hash B291F16A
+ sample 207:
+ time = 4140000
+ flags = 1
+ data = length 13, hash CF820EE0
+ sample 208:
+ time = 4160000
+ flags = 1
+ data = length 13, hash 4E24F683
+ sample 209:
+ time = 4180000
+ flags = 1
+ data = length 13, hash 52BCD68F
+ sample 210:
+ time = 4200000
+ flags = 1
+ data = length 13, hash 42588CB0
+ sample 211:
+ time = 4220000
+ flags = 1
+ data = length 13, hash EBBFECA2
+ sample 212:
+ time = 4240000
+ flags = 1
+ data = length 13, hash C11050CF
+ sample 213:
+ time = 4260000
+ flags = 1
+ data = length 13, hash 6F738603
+ sample 214:
+ time = 4280000
+ flags = 1
+ data = length 13, hash DAD06E5
+ sample 215:
+ time = 4300000
+ flags = 1
+ data = length 13, hash 5B036C64
+ sample 216:
+ time = 4320000
+ flags = 1
+ data = length 13, hash A58DC12E
+ sample 217:
+ time = 4340000
+ flags = 1
+ data = length 13, hash AC59BA7C
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb.amr b/tree/testdata/src/test/assets/amr/sample_wb.amr
similarity index 100%
rename from tree/library/extractor/src/test/assets/amr/sample_wb.amr
rename to tree/testdata/src/test/assets/amr/sample_wb.amr
Binary files differ
diff --git a/tree/testdata/src/test/assets/amr/sample_wb.amr.0.dump b/tree/testdata/src/test/assets/amr/sample_wb.amr.0.dump
new file mode 100644
index 0000000..c4fb207
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb.amr.0.dump
@@ -0,0 +1,690 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4056
+ sample count = 169
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 24, hash C3025798
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 24, hash 39CABAE9
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 24, hash 2752F470
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 24, hash 394F76F6
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 24, hash FF9EEF
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 24, hash 54ECB1B4
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 24, hash 6D7A3A5F
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 24, hash 684CD144
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 24, hash 87B7D176
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 24, hash 4C02F9A5
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 24, hash B4154108
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 24, hash 4448F477
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 24, hash 755A4939
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 24, hash 8C8BC6C3
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 24, hash BC37F63F
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 24, hash 3352C43C
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 24, hash 7998E1F2
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 24, hash A8ECBEFC
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 24, hash 944AC118
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 24, hash FD2C8E1F
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 24, hash B3D867AF
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 24, hash 3DC6E592
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 24, hash 32B276CD
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 24, hash 5488AEF3
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 24, hash 7A4D516
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 24, hash 570AE83F
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 24, hash E5CB3477
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 24, hash E04C00E4
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 24, hash 21B7C97
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 24, hash 1633F470
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 24, hash 28D65CA6
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 24, hash CC6A675C
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 24, hash 4C91080A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 24, hash F6482FB5
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 24, hash 2C76F48C
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 24, hash 6E3B0D72
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 24, hash 799AA003
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 24, hash DFC0BA81
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 24, hash CBDF3826
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 24, hash 16862B75
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 24, hash 865A828E
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 24, hash 336BBDC9
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 24, hash 6CFC6C34
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 24, hash 32C8CD46
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 24, hash 9FE11C4C
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 24, hash AA5A12B7
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 24, hash AA0F4A4D
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 24, hash 34415484
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 24, hash 5018928E
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 24, hash 4A04D162
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 24, hash 4C70F9F0
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 24, hash 99EF3168
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 24, hash C600DAF
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 24, hash FDBB192E
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 24, hash 99096A48
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 24, hash D793F88B
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 24, hash EEB921BD
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 24, hash 8B941A4C
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 24, hash ED5F5FEE
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 24, hash A588E0BB
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 24, hash 588CBC01
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 24, hash DE22266C
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 24, hash 921B6E5C
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 24, hash EC11F041
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 24, hash 5BA9E0A3
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 24, hash DB6D52F3
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 24, hash 8EEBE525
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 24, hash 47A742AE
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 24, hash E93F1E03
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 24, hash 3251F57C
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 24, hash 3EDBBBDD
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 24, hash 2E98465A
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 24, hash A09EA52E
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 24, hash A2A86FA6
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 24, hash 71DCD51C
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 24, hash 2B02DEE1
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 24, hash 7A725192
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 24, hash 929AD483
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 24, hash 68440BF5
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 24, hash 5BD41AD6
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 24, hash 91A381
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 24, hash 8010C408
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 24, hash 482274BE
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 24, hash D7DB8BCC
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 24, hash 680BD9DD
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 24, hash E313577C
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 24, hash 9C10B0CD
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 24, hash 2D90AC02
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 24, hash 64E8C245
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 24, hash 3954AC1B
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 24, hash ACB8999F
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 24, hash 43AE3957
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 24, hash 3C664DB7
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 24, hash 9354B576
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 24, hash B5B9C14E
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 24, hash 7DA9C98F
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 24, hash EFEE54C6
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 24, hash 79DC8CBD
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 24, hash A71A475C
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 24, hash CA1CBB94
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 24, hash 91922226
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 24, hash C90278BC
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 24, hash BD51986F
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 24, hash 90AEF368
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 24, hash 1D83C955
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 24, hash 8FA9A915
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 24, hash C6C753E0
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 24, hash 85FA27A7
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 24, hash A0277324
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 24, hash B7696535
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 24, hash D69D668C
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 24, hash 34C057CD
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_wb.amr.unknown_length.dump b/tree/testdata/src/test/assets/amr/sample_wb.amr.unknown_length.dump
new file mode 100644
index 0000000..c4fb207
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb.amr.unknown_length.dump
@@ -0,0 +1,690 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4056
+ sample count = 169
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 24, hash C3025798
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 24, hash 39CABAE9
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 24, hash 2752F470
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 24, hash 394F76F6
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 24, hash FF9EEF
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 24, hash 54ECB1B4
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 24, hash 6D7A3A5F
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 24, hash 684CD144
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 24, hash 87B7D176
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 24, hash 4C02F9A5
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 24, hash B4154108
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 24, hash 4448F477
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 24, hash 755A4939
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 24, hash 8C8BC6C3
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 24, hash BC37F63F
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 24, hash 3352C43C
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 24, hash 7998E1F2
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 24, hash A8ECBEFC
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 24, hash 944AC118
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 24, hash FD2C8E1F
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 24, hash B3D867AF
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 24, hash 3DC6E592
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 24, hash 32B276CD
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 24, hash 5488AEF3
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 24, hash 7A4D516
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 24, hash 570AE83F
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 24, hash E5CB3477
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 24, hash E04C00E4
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 24, hash 21B7C97
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 24, hash 1633F470
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 24, hash 28D65CA6
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 24, hash CC6A675C
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 24, hash 4C91080A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 24, hash F6482FB5
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 24, hash 2C76F48C
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 24, hash 6E3B0D72
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 24, hash 799AA003
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 24, hash DFC0BA81
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 24, hash CBDF3826
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 24, hash 16862B75
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 24, hash 865A828E
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 24, hash 336BBDC9
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 24, hash 6CFC6C34
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 24, hash 32C8CD46
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 24, hash 9FE11C4C
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 24, hash AA5A12B7
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 24, hash AA0F4A4D
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 24, hash 34415484
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 24, hash 5018928E
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 24, hash 4A04D162
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 24, hash 4C70F9F0
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 24, hash 99EF3168
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 24, hash C600DAF
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 24, hash FDBB192E
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 24, hash 99096A48
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 24, hash D793F88B
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 24, hash EEB921BD
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 24, hash 8B941A4C
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 24, hash ED5F5FEE
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 24, hash A588E0BB
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 24, hash 588CBC01
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 24, hash DE22266C
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 24, hash 921B6E5C
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 24, hash EC11F041
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 24, hash 5BA9E0A3
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 24, hash DB6D52F3
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 24, hash 8EEBE525
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 24, hash 47A742AE
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 24, hash E93F1E03
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 24, hash 3251F57C
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 24, hash 3EDBBBDD
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 24, hash 2E98465A
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 24, hash A09EA52E
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 24, hash A2A86FA6
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 24, hash 71DCD51C
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 24, hash 2B02DEE1
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 24, hash 7A725192
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 24, hash 929AD483
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 24, hash 68440BF5
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 24, hash 5BD41AD6
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 24, hash 91A381
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 24, hash 8010C408
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 24, hash 482274BE
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 24, hash D7DB8BCC
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 24, hash 680BD9DD
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 24, hash E313577C
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 24, hash 9C10B0CD
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 24, hash 2D90AC02
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 24, hash 64E8C245
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 24, hash 3954AC1B
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 24, hash ACB8999F
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 24, hash 43AE3957
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 24, hash 3C664DB7
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 24, hash 9354B576
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 24, hash B5B9C14E
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 24, hash 7DA9C98F
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 24, hash EFEE54C6
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 24, hash 79DC8CBD
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 24, hash A71A475C
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 24, hash CA1CBB94
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 24, hash 91922226
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 24, hash C90278BC
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 24, hash BD51986F
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 24, hash 90AEF368
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 24, hash 1D83C955
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 24, hash 8FA9A915
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 24, hash C6C753E0
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 24, hash 85FA27A7
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 24, hash A0277324
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 24, hash B7696535
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 24, hash D69D668C
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 24, hash 34C057CD
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr
similarity index 100%
rename from tree/library/extractor/src/test/assets/amr/sample_wb_cbr.amr
rename to tree/testdata/src/test/assets/amr/sample_wb_cbr.amr
Binary files differ
diff --git a/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.0.dump b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.0.dump
new file mode 100644
index 0000000..de452ce
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.0.dump
@@ -0,0 +1,693 @@
+seekMap:
+ isSeekable = true
+ duration = 3380000
+ getPosition(0) = [[timeUs=0, position=9]]
+ getPosition(1) = [[timeUs=0, position=9], [timeUs=20000, position=33]]
+ getPosition(1690000) = [[timeUs=1680000, position=2025], [timeUs=1700000, position=2049]]
+ getPosition(3380000) = [[timeUs=3360000, position=4041]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4056
+ sample count = 169
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 24, hash C3025798
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 24, hash 39CABAE9
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 24, hash 2752F470
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 24, hash 394F76F6
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 24, hash FF9EEF
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 24, hash 54ECB1B4
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 24, hash 6D7A3A5F
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 24, hash 684CD144
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 24, hash 87B7D176
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 24, hash 4C02F9A5
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 24, hash B4154108
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 24, hash 4448F477
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 24, hash 755A4939
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 24, hash 8C8BC6C3
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 24, hash BC37F63F
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 24, hash 3352C43C
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 24, hash 7998E1F2
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 24, hash A8ECBEFC
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 24, hash 944AC118
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 24, hash FD2C8E1F
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 24, hash B3D867AF
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 24, hash 3DC6E592
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 24, hash 32B276CD
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 24, hash 5488AEF3
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 24, hash 7A4D516
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 24, hash 570AE83F
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 24, hash E5CB3477
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 24, hash E04C00E4
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 24, hash 21B7C97
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 24, hash 1633F470
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 24, hash 28D65CA6
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 24, hash CC6A675C
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 24, hash 4C91080A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 24, hash F6482FB5
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 24, hash 2C76F48C
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 24, hash 6E3B0D72
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 24, hash 799AA003
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 24, hash DFC0BA81
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 24, hash CBDF3826
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 24, hash 16862B75
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 24, hash 865A828E
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 24, hash 336BBDC9
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 24, hash 6CFC6C34
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 24, hash 32C8CD46
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 24, hash 9FE11C4C
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 24, hash AA5A12B7
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 24, hash AA0F4A4D
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 24, hash 34415484
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 24, hash 5018928E
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 24, hash 4A04D162
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 24, hash 4C70F9F0
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 24, hash 99EF3168
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 24, hash C600DAF
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 24, hash FDBB192E
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 24, hash 99096A48
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 24, hash D793F88B
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 24, hash EEB921BD
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 24, hash 8B941A4C
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 24, hash ED5F5FEE
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 24, hash A588E0BB
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 24, hash 588CBC01
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 24, hash DE22266C
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 24, hash 921B6E5C
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 24, hash EC11F041
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 24, hash 5BA9E0A3
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 24, hash DB6D52F3
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 24, hash 8EEBE525
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 24, hash 47A742AE
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 24, hash E93F1E03
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 24, hash 3251F57C
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 24, hash 3EDBBBDD
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 24, hash 2E98465A
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 24, hash A09EA52E
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 24, hash A2A86FA6
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 24, hash 71DCD51C
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 24, hash 2B02DEE1
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 24, hash 7A725192
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 24, hash 929AD483
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 24, hash 68440BF5
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 24, hash 5BD41AD6
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 24, hash 91A381
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 24, hash 8010C408
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 24, hash 482274BE
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 24, hash D7DB8BCC
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 24, hash 680BD9DD
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 24, hash E313577C
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 24, hash 9C10B0CD
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 24, hash 2D90AC02
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 24, hash 64E8C245
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 24, hash 3954AC1B
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 24, hash ACB8999F
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 24, hash 43AE3957
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 24, hash 3C664DB7
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 24, hash 9354B576
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 24, hash B5B9C14E
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 24, hash 7DA9C98F
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 24, hash EFEE54C6
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 24, hash 79DC8CBD
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 24, hash A71A475C
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 24, hash CA1CBB94
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 24, hash 91922226
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 24, hash C90278BC
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 24, hash BD51986F
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 24, hash 90AEF368
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 24, hash 1D83C955
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 24, hash 8FA9A915
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 24, hash C6C753E0
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 24, hash 85FA27A7
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 24, hash A0277324
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 24, hash B7696535
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 24, hash D69D668C
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 24, hash 34C057CD
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.1.dump b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.1.dump
new file mode 100644
index 0000000..1fc2a1c
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.1.dump
@@ -0,0 +1,469 @@
+seekMap:
+ isSeekable = true
+ duration = 3380000
+ getPosition(0) = [[timeUs=0, position=9]]
+ getPosition(1) = [[timeUs=0, position=9], [timeUs=20000, position=33]]
+ getPosition(1690000) = [[timeUs=1680000, position=2025], [timeUs=1700000, position=2049]]
+ getPosition(3380000) = [[timeUs=3360000, position=4041]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2712
+ sample count = 113
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 1120000
+ flags = 1
+ data = length 24, hash EEB921BD
+ sample 1:
+ time = 1140000
+ flags = 1
+ data = length 24, hash 8B941A4C
+ sample 2:
+ time = 1160000
+ flags = 1
+ data = length 24, hash ED5F5FEE
+ sample 3:
+ time = 1180000
+ flags = 1
+ data = length 24, hash A588E0BB
+ sample 4:
+ time = 1200000
+ flags = 1
+ data = length 24, hash 588CBC01
+ sample 5:
+ time = 1220000
+ flags = 1
+ data = length 24, hash DE22266C
+ sample 6:
+ time = 1240000
+ flags = 1
+ data = length 24, hash 921B6E5C
+ sample 7:
+ time = 1260000
+ flags = 1
+ data = length 24, hash EC11F041
+ sample 8:
+ time = 1280000
+ flags = 1
+ data = length 24, hash 5BA9E0A3
+ sample 9:
+ time = 1300000
+ flags = 1
+ data = length 24, hash DB6D52F3
+ sample 10:
+ time = 1320000
+ flags = 1
+ data = length 24, hash 8EEBE525
+ sample 11:
+ time = 1340000
+ flags = 1
+ data = length 24, hash 47A742AE
+ sample 12:
+ time = 1360000
+ flags = 1
+ data = length 24, hash E93F1E03
+ sample 13:
+ time = 1380000
+ flags = 1
+ data = length 24, hash 3251F57C
+ sample 14:
+ time = 1400000
+ flags = 1
+ data = length 24, hash 3EDBBBDD
+ sample 15:
+ time = 1420000
+ flags = 1
+ data = length 24, hash 2E98465A
+ sample 16:
+ time = 1440000
+ flags = 1
+ data = length 24, hash A09EA52E
+ sample 17:
+ time = 1460000
+ flags = 1
+ data = length 24, hash A2A86FA6
+ sample 18:
+ time = 1480000
+ flags = 1
+ data = length 24, hash 71DCD51C
+ sample 19:
+ time = 1500000
+ flags = 1
+ data = length 24, hash 2B02DEE1
+ sample 20:
+ time = 1520000
+ flags = 1
+ data = length 24, hash 7A725192
+ sample 21:
+ time = 1540000
+ flags = 1
+ data = length 24, hash 929AD483
+ sample 22:
+ time = 1560000
+ flags = 1
+ data = length 24, hash 68440BF5
+ sample 23:
+ time = 1580000
+ flags = 1
+ data = length 24, hash 5BD41AD6
+ sample 24:
+ time = 1600000
+ flags = 1
+ data = length 24, hash 91A381
+ sample 25:
+ time = 1620000
+ flags = 1
+ data = length 24, hash 8010C408
+ sample 26:
+ time = 1640000
+ flags = 1
+ data = length 24, hash 482274BE
+ sample 27:
+ time = 1660000
+ flags = 1
+ data = length 24, hash D7DB8BCC
+ sample 28:
+ time = 1680000
+ flags = 1
+ data = length 24, hash 680BD9DD
+ sample 29:
+ time = 1700000
+ flags = 1
+ data = length 24, hash E313577C
+ sample 30:
+ time = 1720000
+ flags = 1
+ data = length 24, hash 9C10B0CD
+ sample 31:
+ time = 1740000
+ flags = 1
+ data = length 24, hash 2D90AC02
+ sample 32:
+ time = 1760000
+ flags = 1
+ data = length 24, hash 64E8C245
+ sample 33:
+ time = 1780000
+ flags = 1
+ data = length 24, hash 3954AC1B
+ sample 34:
+ time = 1800000
+ flags = 1
+ data = length 24, hash ACB8999F
+ sample 35:
+ time = 1820000
+ flags = 1
+ data = length 24, hash 43AE3957
+ sample 36:
+ time = 1840000
+ flags = 1
+ data = length 24, hash 3C664DB7
+ sample 37:
+ time = 1860000
+ flags = 1
+ data = length 24, hash 9354B576
+ sample 38:
+ time = 1880000
+ flags = 1
+ data = length 24, hash B5B9C14E
+ sample 39:
+ time = 1900000
+ flags = 1
+ data = length 24, hash 7DA9C98F
+ sample 40:
+ time = 1920000
+ flags = 1
+ data = length 24, hash EFEE54C6
+ sample 41:
+ time = 1940000
+ flags = 1
+ data = length 24, hash 79DC8CBD
+ sample 42:
+ time = 1960000
+ flags = 1
+ data = length 24, hash A71A475C
+ sample 43:
+ time = 1980000
+ flags = 1
+ data = length 24, hash CA1CBB94
+ sample 44:
+ time = 2000000
+ flags = 1
+ data = length 24, hash 91922226
+ sample 45:
+ time = 2020000
+ flags = 1
+ data = length 24, hash C90278BC
+ sample 46:
+ time = 2040000
+ flags = 1
+ data = length 24, hash BD51986F
+ sample 47:
+ time = 2060000
+ flags = 1
+ data = length 24, hash 90AEF368
+ sample 48:
+ time = 2080000
+ flags = 1
+ data = length 24, hash 1D83C955
+ sample 49:
+ time = 2100000
+ flags = 1
+ data = length 24, hash 8FA9A915
+ sample 50:
+ time = 2120000
+ flags = 1
+ data = length 24, hash C6C753E0
+ sample 51:
+ time = 2140000
+ flags = 1
+ data = length 24, hash 85FA27A7
+ sample 52:
+ time = 2160000
+ flags = 1
+ data = length 24, hash A0277324
+ sample 53:
+ time = 2180000
+ flags = 1
+ data = length 24, hash B7696535
+ sample 54:
+ time = 2200000
+ flags = 1
+ data = length 24, hash D69D668C
+ sample 55:
+ time = 2220000
+ flags = 1
+ data = length 24, hash 34C057CD
+ sample 56:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 57:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 58:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 59:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 60:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 61:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 62:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 63:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 64:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 65:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 66:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 67:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 68:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 69:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 70:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 71:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 72:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 73:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 74:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 75:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 76:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 77:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 78:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 79:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 80:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 81:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 82:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 83:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 84:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 85:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 86:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 87:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 88:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 89:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 90:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 91:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 92:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 93:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 94:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 95:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 96:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 97:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 98:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 99:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 100:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 101:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 102:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 103:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 104:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 105:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 106:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 107:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 108:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 109:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 110:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 111:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 112:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.2.dump b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.2.dump
new file mode 100644
index 0000000..4f41388
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.2.dump
@@ -0,0 +1,245 @@
+seekMap:
+ isSeekable = true
+ duration = 3380000
+ getPosition(0) = [[timeUs=0, position=9]]
+ getPosition(1) = [[timeUs=0, position=9], [timeUs=20000, position=33]]
+ getPosition(1690000) = [[timeUs=1680000, position=2025], [timeUs=1700000, position=2049]]
+ getPosition(3380000) = [[timeUs=3360000, position=4041]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 1368
+ sample count = 57
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 1:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 2:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 3:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 4:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 5:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 6:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 7:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 8:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 9:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 10:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 11:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 12:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 13:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 14:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 15:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 16:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 17:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 18:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 19:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 20:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 21:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 22:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 23:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 24:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 25:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 26:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 27:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 28:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 29:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 30:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 31:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 32:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 33:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 34:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 35:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 36:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 37:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 38:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 39:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 40:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 41:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 42:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 43:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 44:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 45:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 46:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 47:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 48:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 49:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 50:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 51:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 52:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 53:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 54:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 55:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 56:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.3.dump b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.3.dump
new file mode 100644
index 0000000..6dfb762
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.3.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 3380000
+ getPosition(0) = [[timeUs=0, position=9]]
+ getPosition(1) = [[timeUs=0, position=9], [timeUs=20000, position=33]]
+ getPosition(1690000) = [[timeUs=1680000, position=2025], [timeUs=1700000, position=2049]]
+ getPosition(3380000) = [[timeUs=3360000, position=4041]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 24
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.unknown_length.dump b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.unknown_length.dump
new file mode 100644
index 0000000..c4fb207
--- /dev/null
+++ b/tree/testdata/src/test/assets/amr/sample_wb_cbr.amr.unknown_length.dump
@@ -0,0 +1,690 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4056
+ sample count = 169
+ format 0:
+ sampleMimeType = audio/amr-wb
+ maxInputSize = 61
+ channelCount = 1
+ sampleRate = 16000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 24, hash C3025798
+ sample 1:
+ time = 20000
+ flags = 1
+ data = length 24, hash 39CABAE9
+ sample 2:
+ time = 40000
+ flags = 1
+ data = length 24, hash 2752F470
+ sample 3:
+ time = 60000
+ flags = 1
+ data = length 24, hash 394F76F6
+ sample 4:
+ time = 80000
+ flags = 1
+ data = length 24, hash FF9EEF
+ sample 5:
+ time = 100000
+ flags = 1
+ data = length 24, hash 54ECB1B4
+ sample 6:
+ time = 120000
+ flags = 1
+ data = length 24, hash 6D7A3A5F
+ sample 7:
+ time = 140000
+ flags = 1
+ data = length 24, hash 684CD144
+ sample 8:
+ time = 160000
+ flags = 1
+ data = length 24, hash 87B7D176
+ sample 9:
+ time = 180000
+ flags = 1
+ data = length 24, hash 4C02F9A5
+ sample 10:
+ time = 200000
+ flags = 1
+ data = length 24, hash B4154108
+ sample 11:
+ time = 220000
+ flags = 1
+ data = length 24, hash 4448F477
+ sample 12:
+ time = 240000
+ flags = 1
+ data = length 24, hash 755A4939
+ sample 13:
+ time = 260000
+ flags = 1
+ data = length 24, hash 8C8BC6C3
+ sample 14:
+ time = 280000
+ flags = 1
+ data = length 24, hash BC37F63F
+ sample 15:
+ time = 300000
+ flags = 1
+ data = length 24, hash 3352C43C
+ sample 16:
+ time = 320000
+ flags = 1
+ data = length 24, hash 7998E1F2
+ sample 17:
+ time = 340000
+ flags = 1
+ data = length 24, hash A8ECBEFC
+ sample 18:
+ time = 360000
+ flags = 1
+ data = length 24, hash 944AC118
+ sample 19:
+ time = 380000
+ flags = 1
+ data = length 24, hash FD2C8E1F
+ sample 20:
+ time = 400000
+ flags = 1
+ data = length 24, hash B3D867AF
+ sample 21:
+ time = 420000
+ flags = 1
+ data = length 24, hash 3DC6E592
+ sample 22:
+ time = 440000
+ flags = 1
+ data = length 24, hash 32B276CD
+ sample 23:
+ time = 460000
+ flags = 1
+ data = length 24, hash 5488AEF3
+ sample 24:
+ time = 480000
+ flags = 1
+ data = length 24, hash 7A4D516
+ sample 25:
+ time = 500000
+ flags = 1
+ data = length 24, hash 570AE83F
+ sample 26:
+ time = 520000
+ flags = 1
+ data = length 24, hash E5CB3477
+ sample 27:
+ time = 540000
+ flags = 1
+ data = length 24, hash E04C00E4
+ sample 28:
+ time = 560000
+ flags = 1
+ data = length 24, hash 21B7C97
+ sample 29:
+ time = 580000
+ flags = 1
+ data = length 24, hash 1633F470
+ sample 30:
+ time = 600000
+ flags = 1
+ data = length 24, hash 28D65CA6
+ sample 31:
+ time = 620000
+ flags = 1
+ data = length 24, hash CC6A675C
+ sample 32:
+ time = 640000
+ flags = 1
+ data = length 24, hash 4C91080A
+ sample 33:
+ time = 660000
+ flags = 1
+ data = length 24, hash F6482FB5
+ sample 34:
+ time = 680000
+ flags = 1
+ data = length 24, hash 2C76F48C
+ sample 35:
+ time = 700000
+ flags = 1
+ data = length 24, hash 6E3B0D72
+ sample 36:
+ time = 720000
+ flags = 1
+ data = length 24, hash 799AA003
+ sample 37:
+ time = 740000
+ flags = 1
+ data = length 24, hash DFC0BA81
+ sample 38:
+ time = 760000
+ flags = 1
+ data = length 24, hash CBDF3826
+ sample 39:
+ time = 780000
+ flags = 1
+ data = length 24, hash 16862B75
+ sample 40:
+ time = 800000
+ flags = 1
+ data = length 24, hash 865A828E
+ sample 41:
+ time = 820000
+ flags = 1
+ data = length 24, hash 336BBDC9
+ sample 42:
+ time = 840000
+ flags = 1
+ data = length 24, hash 6CFC6C34
+ sample 43:
+ time = 860000
+ flags = 1
+ data = length 24, hash 32C8CD46
+ sample 44:
+ time = 880000
+ flags = 1
+ data = length 24, hash 9FE11C4C
+ sample 45:
+ time = 900000
+ flags = 1
+ data = length 24, hash AA5A12B7
+ sample 46:
+ time = 920000
+ flags = 1
+ data = length 24, hash AA0F4A4D
+ sample 47:
+ time = 940000
+ flags = 1
+ data = length 24, hash 34415484
+ sample 48:
+ time = 960000
+ flags = 1
+ data = length 24, hash 5018928E
+ sample 49:
+ time = 980000
+ flags = 1
+ data = length 24, hash 4A04D162
+ sample 50:
+ time = 1000000
+ flags = 1
+ data = length 24, hash 4C70F9F0
+ sample 51:
+ time = 1020000
+ flags = 1
+ data = length 24, hash 99EF3168
+ sample 52:
+ time = 1040000
+ flags = 1
+ data = length 24, hash C600DAF
+ sample 53:
+ time = 1060000
+ flags = 1
+ data = length 24, hash FDBB192E
+ sample 54:
+ time = 1080000
+ flags = 1
+ data = length 24, hash 99096A48
+ sample 55:
+ time = 1100000
+ flags = 1
+ data = length 24, hash D793F88B
+ sample 56:
+ time = 1120000
+ flags = 1
+ data = length 24, hash EEB921BD
+ sample 57:
+ time = 1140000
+ flags = 1
+ data = length 24, hash 8B941A4C
+ sample 58:
+ time = 1160000
+ flags = 1
+ data = length 24, hash ED5F5FEE
+ sample 59:
+ time = 1180000
+ flags = 1
+ data = length 24, hash A588E0BB
+ sample 60:
+ time = 1200000
+ flags = 1
+ data = length 24, hash 588CBC01
+ sample 61:
+ time = 1220000
+ flags = 1
+ data = length 24, hash DE22266C
+ sample 62:
+ time = 1240000
+ flags = 1
+ data = length 24, hash 921B6E5C
+ sample 63:
+ time = 1260000
+ flags = 1
+ data = length 24, hash EC11F041
+ sample 64:
+ time = 1280000
+ flags = 1
+ data = length 24, hash 5BA9E0A3
+ sample 65:
+ time = 1300000
+ flags = 1
+ data = length 24, hash DB6D52F3
+ sample 66:
+ time = 1320000
+ flags = 1
+ data = length 24, hash 8EEBE525
+ sample 67:
+ time = 1340000
+ flags = 1
+ data = length 24, hash 47A742AE
+ sample 68:
+ time = 1360000
+ flags = 1
+ data = length 24, hash E93F1E03
+ sample 69:
+ time = 1380000
+ flags = 1
+ data = length 24, hash 3251F57C
+ sample 70:
+ time = 1400000
+ flags = 1
+ data = length 24, hash 3EDBBBDD
+ sample 71:
+ time = 1420000
+ flags = 1
+ data = length 24, hash 2E98465A
+ sample 72:
+ time = 1440000
+ flags = 1
+ data = length 24, hash A09EA52E
+ sample 73:
+ time = 1460000
+ flags = 1
+ data = length 24, hash A2A86FA6
+ sample 74:
+ time = 1480000
+ flags = 1
+ data = length 24, hash 71DCD51C
+ sample 75:
+ time = 1500000
+ flags = 1
+ data = length 24, hash 2B02DEE1
+ sample 76:
+ time = 1520000
+ flags = 1
+ data = length 24, hash 7A725192
+ sample 77:
+ time = 1540000
+ flags = 1
+ data = length 24, hash 929AD483
+ sample 78:
+ time = 1560000
+ flags = 1
+ data = length 24, hash 68440BF5
+ sample 79:
+ time = 1580000
+ flags = 1
+ data = length 24, hash 5BD41AD6
+ sample 80:
+ time = 1600000
+ flags = 1
+ data = length 24, hash 91A381
+ sample 81:
+ time = 1620000
+ flags = 1
+ data = length 24, hash 8010C408
+ sample 82:
+ time = 1640000
+ flags = 1
+ data = length 24, hash 482274BE
+ sample 83:
+ time = 1660000
+ flags = 1
+ data = length 24, hash D7DB8BCC
+ sample 84:
+ time = 1680000
+ flags = 1
+ data = length 24, hash 680BD9DD
+ sample 85:
+ time = 1700000
+ flags = 1
+ data = length 24, hash E313577C
+ sample 86:
+ time = 1720000
+ flags = 1
+ data = length 24, hash 9C10B0CD
+ sample 87:
+ time = 1740000
+ flags = 1
+ data = length 24, hash 2D90AC02
+ sample 88:
+ time = 1760000
+ flags = 1
+ data = length 24, hash 64E8C245
+ sample 89:
+ time = 1780000
+ flags = 1
+ data = length 24, hash 3954AC1B
+ sample 90:
+ time = 1800000
+ flags = 1
+ data = length 24, hash ACB8999F
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 24, hash 43AE3957
+ sample 92:
+ time = 1840000
+ flags = 1
+ data = length 24, hash 3C664DB7
+ sample 93:
+ time = 1860000
+ flags = 1
+ data = length 24, hash 9354B576
+ sample 94:
+ time = 1880000
+ flags = 1
+ data = length 24, hash B5B9C14E
+ sample 95:
+ time = 1900000
+ flags = 1
+ data = length 24, hash 7DA9C98F
+ sample 96:
+ time = 1920000
+ flags = 1
+ data = length 24, hash EFEE54C6
+ sample 97:
+ time = 1940000
+ flags = 1
+ data = length 24, hash 79DC8CBD
+ sample 98:
+ time = 1960000
+ flags = 1
+ data = length 24, hash A71A475C
+ sample 99:
+ time = 1980000
+ flags = 1
+ data = length 24, hash CA1CBB94
+ sample 100:
+ time = 2000000
+ flags = 1
+ data = length 24, hash 91922226
+ sample 101:
+ time = 2020000
+ flags = 1
+ data = length 24, hash C90278BC
+ sample 102:
+ time = 2040000
+ flags = 1
+ data = length 24, hash BD51986F
+ sample 103:
+ time = 2060000
+ flags = 1
+ data = length 24, hash 90AEF368
+ sample 104:
+ time = 2080000
+ flags = 1
+ data = length 24, hash 1D83C955
+ sample 105:
+ time = 2100000
+ flags = 1
+ data = length 24, hash 8FA9A915
+ sample 106:
+ time = 2120000
+ flags = 1
+ data = length 24, hash C6C753E0
+ sample 107:
+ time = 2140000
+ flags = 1
+ data = length 24, hash 85FA27A7
+ sample 108:
+ time = 2160000
+ flags = 1
+ data = length 24, hash A0277324
+ sample 109:
+ time = 2180000
+ flags = 1
+ data = length 24, hash B7696535
+ sample 110:
+ time = 2200000
+ flags = 1
+ data = length 24, hash D69D668C
+ sample 111:
+ time = 2220000
+ flags = 1
+ data = length 24, hash 34C057CD
+ sample 112:
+ time = 2240000
+ flags = 1
+ data = length 24, hash 4EC5E974
+ sample 113:
+ time = 2260000
+ flags = 1
+ data = length 24, hash 1C1CD40D
+ sample 114:
+ time = 2280000
+ flags = 1
+ data = length 24, hash 76CC54BC
+ sample 115:
+ time = 2300000
+ flags = 1
+ data = length 24, hash D497ACF5
+ sample 116:
+ time = 2320000
+ flags = 1
+ data = length 24, hash A1386080
+ sample 117:
+ time = 2340000
+ flags = 1
+ data = length 24, hash 7ED36954
+ sample 118:
+ time = 2360000
+ flags = 1
+ data = length 24, hash C11A3BF9
+ sample 119:
+ time = 2380000
+ flags = 1
+ data = length 24, hash 8FB69488
+ sample 120:
+ time = 2400000
+ flags = 1
+ data = length 24, hash C6225F59
+ sample 121:
+ time = 2420000
+ flags = 1
+ data = length 24, hash 122AB6D2
+ sample 122:
+ time = 2440000
+ flags = 1
+ data = length 24, hash 1E195E7D
+ sample 123:
+ time = 2460000
+ flags = 1
+ data = length 24, hash BD3DF418
+ sample 124:
+ time = 2480000
+ flags = 1
+ data = length 24, hash D8AE4A5
+ sample 125:
+ time = 2500000
+ flags = 1
+ data = length 24, hash 977BD182
+ sample 126:
+ time = 2520000
+ flags = 1
+ data = length 24, hash F361F060
+ sample 127:
+ time = 2540000
+ flags = 1
+ data = length 24, hash 11EC8CD0
+ sample 128:
+ time = 2560000
+ flags = 1
+ data = length 24, hash 3798F3D2
+ sample 129:
+ time = 2580000
+ flags = 1
+ data = length 24, hash B2C2517C
+ sample 130:
+ time = 2600000
+ flags = 1
+ data = length 24, hash FBE0D0D8
+ sample 131:
+ time = 2620000
+ flags = 1
+ data = length 24, hash 7033172F
+ sample 132:
+ time = 2640000
+ flags = 1
+ data = length 24, hash BE760029
+ sample 133:
+ time = 2660000
+ flags = 1
+ data = length 24, hash 590AF28C
+ sample 134:
+ time = 2680000
+ flags = 1
+ data = length 24, hash AD28C48F
+ sample 135:
+ time = 2700000
+ flags = 1
+ data = length 24, hash 640AA61B
+ sample 136:
+ time = 2720000
+ flags = 1
+ data = length 24, hash ABE659B
+ sample 137:
+ time = 2740000
+ flags = 1
+ data = length 24, hash ED2691D2
+ sample 138:
+ time = 2760000
+ flags = 1
+ data = length 24, hash D998C80E
+ sample 139:
+ time = 2780000
+ flags = 1
+ data = length 24, hash 8DC0DF5C
+ sample 140:
+ time = 2800000
+ flags = 1
+ data = length 24, hash 7692247B
+ sample 141:
+ time = 2820000
+ flags = 1
+ data = length 24, hash C1D1CCB9
+ sample 142:
+ time = 2840000
+ flags = 1
+ data = length 24, hash 362CE78E
+ sample 143:
+ time = 2860000
+ flags = 1
+ data = length 24, hash 54FA84A
+ sample 144:
+ time = 2880000
+ flags = 1
+ data = length 24, hash 29E88C84
+ sample 145:
+ time = 2900000
+ flags = 1
+ data = length 24, hash 1CD848AC
+ sample 146:
+ time = 2920000
+ flags = 1
+ data = length 24, hash 5C3D4A79
+ sample 147:
+ time = 2940000
+ flags = 1
+ data = length 24, hash 1AA8E604
+ sample 148:
+ time = 2960000
+ flags = 1
+ data = length 24, hash 186A4316
+ sample 149:
+ time = 2980000
+ flags = 1
+ data = length 24, hash 61ACE481
+ sample 150:
+ time = 3000000
+ flags = 1
+ data = length 24, hash D0C42780
+ sample 151:
+ time = 3020000
+ flags = 1
+ data = length 24, hash FAD51BA1
+ sample 152:
+ time = 3040000
+ flags = 1
+ data = length 24, hash F1A9AC71
+ sample 153:
+ time = 3060000
+ flags = 1
+ data = length 24, hash 24425449
+ sample 154:
+ time = 3080000
+ flags = 1
+ data = length 24, hash 37AAC3E6
+ sample 155:
+ time = 3100000
+ flags = 1
+ data = length 24, hash 91F68CB4
+ sample 156:
+ time = 3120000
+ flags = 1
+ data = length 24, hash F8C92820
+ sample 157:
+ time = 3140000
+ flags = 1
+ data = length 24, hash ECD39C3E
+ sample 158:
+ time = 3160000
+ flags = 1
+ data = length 24, hash B27D8F78
+ sample 159:
+ time = 3180000
+ flags = 1
+ data = length 24, hash C9EB3DFB
+ sample 160:
+ time = 3200000
+ flags = 1
+ data = length 24, hash 88DC54A2
+ sample 161:
+ time = 3220000
+ flags = 1
+ data = length 24, hash 7FC4C5BE
+ sample 162:
+ time = 3240000
+ flags = 1
+ data = length 24, hash E4F684EF
+ sample 163:
+ time = 3260000
+ flags = 1
+ data = length 24, hash 55C08B56
+ sample 164:
+ time = 3280000
+ flags = 1
+ data = length 24, hash E5A0F006
+ sample 165:
+ time = 3300000
+ flags = 1
+ data = length 24, hash DE3F3AA7
+ sample 166:
+ time = 3320000
+ flags = 1
+ data = length 24, hash 3F28AE7F
+ sample 167:
+ time = 3340000
+ flags = 1
+ data = length 24, hash 3949CAFF
+ sample 168:
+ time = 3360000
+ flags = 1
+ data = length 24, hash 772665A0
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/binary/ogg/vorbis_header_pages b/tree/testdata/src/test/assets/binary/ogg/vorbis_header_pages
similarity index 100%
rename from tree/library/extractor/src/test/assets/binary/ogg/vorbis_header_pages
rename to tree/testdata/src/test/assets/binary/ogg/vorbis_header_pages
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/binary/vorbis/comment_header b/tree/testdata/src/test/assets/binary/vorbis/comment_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/binary/vorbis/comment_header
rename to tree/testdata/src/test/assets/binary/vorbis/comment_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/binary/vorbis/id_header b/tree/testdata/src/test/assets/binary/vorbis/id_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/binary/vorbis/id_header
rename to tree/testdata/src/test/assets/binary/vorbis/id_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/binary/vorbis/setup_header b/tree/testdata/src/test/assets/binary/vorbis/setup_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/binary/vorbis/setup_header
rename to tree/testdata/src/test/assets/binary/vorbis/setup_header
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/bitmap/image_256_256.png b/tree/testdata/src/test/assets/bitmap/image_256_256.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/bitmap/image_256_256.png
rename to tree/testdata/src/test/assets/bitmap/image_256_256.png
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/bitmap/image_80_60.bmp b/tree/testdata/src/test/assets/bitmap/image_80_60.bmp
similarity index 100%
rename from tree/library/core/src/androidTest/assets/bitmap/image_80_60.bmp
rename to tree/testdata/src/test/assets/bitmap/image_80_60.bmp
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/dash-download-v0 b/tree/testdata/src/test/assets/download-actions/dash-download-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/dash-download-v0
rename to tree/testdata/src/test/assets/download-actions/dash-download-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/dash-remove-v0 b/tree/testdata/src/test/assets/download-actions/dash-remove-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/dash-remove-v0
rename to tree/testdata/src/test/assets/download-actions/dash-remove-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/hls-download-v0 b/tree/testdata/src/test/assets/download-actions/hls-download-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/hls-download-v0
rename to tree/testdata/src/test/assets/download-actions/hls-download-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/hls-download-v1 b/tree/testdata/src/test/assets/download-actions/hls-download-v1
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/hls-download-v1
rename to tree/testdata/src/test/assets/download-actions/hls-download-v1
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/hls-remove-v0 b/tree/testdata/src/test/assets/download-actions/hls-remove-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/hls-remove-v0
rename to tree/testdata/src/test/assets/download-actions/hls-remove-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/hls-remove-v1 b/tree/testdata/src/test/assets/download-actions/hls-remove-v1
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/hls-remove-v1
rename to tree/testdata/src/test/assets/download-actions/hls-remove-v1
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/progressive-download-v0 b/tree/testdata/src/test/assets/download-actions/progressive-download-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/progressive-download-v0
rename to tree/testdata/src/test/assets/download-actions/progressive-download-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/progressive-remove-v0 b/tree/testdata/src/test/assets/download-actions/progressive-remove-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/progressive-remove-v0
rename to tree/testdata/src/test/assets/download-actions/progressive-remove-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/ss-download-v0 b/tree/testdata/src/test/assets/download-actions/ss-download-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/ss-download-v0
rename to tree/testdata/src/test/assets/download-actions/ss-download-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/ss-download-v1 b/tree/testdata/src/test/assets/download-actions/ss-download-v1
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/ss-download-v1
rename to tree/testdata/src/test/assets/download-actions/ss-download-v1
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/ss-remove-v0 b/tree/testdata/src/test/assets/download-actions/ss-remove-v0
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/ss-remove-v0
rename to tree/testdata/src/test/assets/download-actions/ss-remove-v0
Binary files differ
diff --git a/tree/library/core/src/test/assets/download-actions/ss-remove-v1 b/tree/testdata/src/test/assets/download-actions/ss-remove-v1
similarity index 100%
rename from tree/library/core/src/test/assets/download-actions/ss-remove-v1
rename to tree/testdata/src/test/assets/download-actions/ss-remove-v1
Binary files differ
diff --git a/tree/testdata/src/test/assets/dvbsi/README.md b/tree/testdata/src/test/assets/dvbsi/README.md
new file mode 100644
index 0000000..b17512a
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/README.md
@@ -0,0 +1,15 @@
+# DVB Test Data
+
+The `.bin` files in this directory are generated from the `.xml` files using
+`tstabcomp` from [TSDuck](https://tsduck.io/).
+
+The XML files are kept to make it clear where the values in the test assertions
+are coming from, and to make it easier to change or add data in future. When
+adding new files, or making changes to existing ones, you should regenerate the
+`.bin` files using the command above before committing.
+
+To regenerate all the `.bin` files:
+
+```shell
+$ tstabcomp -c testdata/src/test/assets/dvbsi/*.xml
+```
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.bin b/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.bin
new file mode 100644
index 0000000..b1215d5
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.bin
Binary files differ
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.xml b/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.xml
new file mode 100644
index 0000000..0b54643
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_no_url_base.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tsduck>
+ <AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
+ <application control_code="0x01">
+ <application_identifier organization_id="0x00000120" application_id="0x0071"/>
+ <transport_protocol_descriptor transport_protocol_label="0x00">
+ <http>
+ </http>
+ </transport_protocol_descriptor>
+ <simple_application_location_descriptor initial_path="foo/bar"/>
+ </application>
+ </AIT>
+</tsduck>
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.bin b/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.bin
new file mode 100644
index 0000000..4fedaf6
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.bin
Binary files differ
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.xml b/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.xml
new file mode 100644
index 0000000..0bc17fc
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_no_url_path.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<tsduck>
+ <AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
+ <application control_code="0x01">
+ <application_identifier organization_id="0x00000120" application_id="0x0071"/>
+ <transport_protocol_descriptor transport_protocol_label="0x00">
+ <http>
+ <url base="http://google.com/"/>
+ </http>
+ </transport_protocol_descriptor>
+ </application>
+ </AIT>
+</tsduck>
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_typical.bin b/tree/testdata/src/test/assets/dvbsi/ait_typical.bin
new file mode 100644
index 0000000..0ab2d3f
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_typical.bin
Binary files differ
diff --git a/tree/testdata/src/test/assets/dvbsi/ait_typical.xml b/tree/testdata/src/test/assets/dvbsi/ait_typical.xml
new file mode 100644
index 0000000..1da4178
--- /dev/null
+++ b/tree/testdata/src/test/assets/dvbsi/ait_typical.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tsduck>
+ <AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
+ <application control_code="0x01">
+ <application_identifier organization_id="0x00000120" application_id="0x0071"/>
+ <transport_protocol_descriptor transport_protocol_label="0x00">
+ <http>
+ <url base="http://example.com/"/>
+ </http>
+ </transport_protocol_descriptor>
+ <simple_application_location_descriptor initial_path="path/foo"/>
+ </application>
+ <application control_code="0x02">
+ <application_identifier organization_id="0x00000120" application_id="0x0072"/>
+ <transport_protocol_descriptor transport_protocol_label="0x00">
+ <http>
+ <url base="http://google.com/"/>
+ </http>
+ </transport_protocol_descriptor>
+ <simple_application_location_descriptor initial_path="path/bar"/>
+ </application>
+ </AIT>
+</tsduck>
diff --git a/tree/library/extractor/src/test/assets/flac/bear.flac b/tree/testdata/src/test/assets/flac/bear.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear.flac
rename to tree/testdata/src/test/assets/flac/bear.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_flac.0.dump
new file mode 100644
index 0000000..6b9ba35
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_flac.0.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_flac.1.dump
new file mode 100644
index 0000000..dc636b9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_flac.1.dump
@@ -0,0 +1,111 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_flac.2.dump
new file mode 100644
index 0000000..6562202
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_flac.2.dump
@@ -0,0 +1,67 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_flac.3.dump
new file mode 100644
index 0000000..a12d386
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_flac.3.dump
@@ -0,0 +1,27 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_flac.unknown_length.dump
new file mode 100644
index 0000000..6b9ba35
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_flac.unknown_length.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_no_min_max_frame_size.flac
rename to tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.0.dump
new file mode 100644
index 0000000..3a94f57
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.0.dump
@@ -0,0 +1,150 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 9218FDB7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.1.dump
new file mode 100644
index 0000000..a101b03
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.1.dump
@@ -0,0 +1,110 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 9218FDB7
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.2.dump
new file mode 100644
index 0000000..6e72772
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.2.dump
@@ -0,0 +1,66 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 9218FDB7
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.3.dump
new file mode 100644
index 0000000..566c93c
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.3.dump
@@ -0,0 +1,26 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 9218FDB7
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.unknown_length.dump
new file mode 100644
index 0000000..3a94f57
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_flac.unknown_length.dump
@@ -0,0 +1,150 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 9218FDB7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.0.dump
new file mode 100644
index 0000000..37eee44
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.1.dump
new file mode 100644
index 0000000..2c29925
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.2.dump
new file mode 100644
index 0000000..86d36c9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.3.dump
new file mode 100644
index 0000000..2ad8679
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.unknown_length.dump
new file mode 100644
index 0000000..37eee44
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_min_max_frame_size_raw.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_num_samples.flac b/tree/testdata/src/test/assets/flac/bear_no_num_samples.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_no_num_samples.flac
rename to tree/testdata/src/test/assets/flac/bear_no_num_samples.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.0.dump
new file mode 100644
index 0000000..072524e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.0.dump
@@ -0,0 +1,149 @@
+seekMap:
+ isSeekable = true
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 49FA2C21
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.unknown_length.dump
new file mode 100644
index 0000000..072524e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_num_samples_flac.unknown_length.dump
@@ -0,0 +1,149 @@
+seekMap:
+ isSeekable = true
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 49FA2C21
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.0.dump
new file mode 100644
index 0000000..9615182
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.0.dump
@@ -0,0 +1,150 @@
+seekMap:
+ isSeekable = true
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.unknown_length.dump
new file mode 100644
index 0000000..9615182
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_num_samples_raw.unknown_length.dump
@@ -0,0 +1,150 @@
+seekMap:
+ isSeekable = true
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac
rename to tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.0.dump
new file mode 100644
index 0000000..28aa56e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.0.dump
@@ -0,0 +1,148 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 49FA2C21
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.unknown_length.dump
new file mode 100644
index 0000000..28aa56e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_flac.unknown_length.dump
@@ -0,0 +1,148 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 49FA2C21
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.0.dump
new file mode 100644
index 0000000..34a2535
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.0.dump
@@ -0,0 +1,149 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.unknown_length.dump
new file mode 100644
index 0000000..34a2535
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_no_seek_table_no_num_samples_raw.unknown_length.dump
@@ -0,0 +1,149 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac b/tree/testdata/src/test/assets/flac/bear_one_metadata_block.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_one_metadata_block.flac
rename to tree/testdata/src/test/assets/flac/bear_one_metadata_block.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.0.dump
new file mode 100644
index 0000000..2c97070
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.0.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.1.dump
new file mode 100644
index 0000000..efb7caa
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.1.dump
@@ -0,0 +1,111 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.2.dump
new file mode 100644
index 0000000..851efbc
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.2.dump
@@ -0,0 +1,67 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.3.dump
new file mode 100644
index 0000000..c699876
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 445
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.unknown_length.dump
new file mode 100644
index 0000000..80dd2e9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_flac.unknown_length.dump
@@ -0,0 +1,148 @@
+seekMap:
+ isSeekable = false
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.0.dump
new file mode 100644
index 0000000..172f9e4
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.1.dump
new file mode 100644
index 0000000..cacb89d
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.2.dump
new file mode 100644
index 0000000..8285b73
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.3.dump
new file mode 100644
index 0000000..3223ab2
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.3.dump
@@ -0,0 +1,24 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=42]]
+ getPosition(1) = [[timeUs=1, position=42]]
+ getPosition(1370500) = [[timeUs=1370500, position=75036]]
+ getPosition(2741000) = [[timeUs=2741000, position=153139]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 1984
+ sample count = 1
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.unknown_length.dump
new file mode 100644
index 0000000..0c9b3e7
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_one_metadata_block_raw.unknown_length.dump
@@ -0,0 +1,149 @@
+seekMap:
+ isSeekable = false
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_raw.0.dump
new file mode 100644
index 0000000..37eee44
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_raw.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_raw.1.dump
new file mode 100644
index 0000000..2c29925
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_raw.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_raw.2.dump
new file mode 100644
index 0000000..86d36c9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_raw.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_raw.3.dump
new file mode 100644
index 0000000..2ad8679
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_raw.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_raw.unknown_length.dump
new file mode 100644
index 0000000..37eee44
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_raw.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_uncommon_sample_rate.flac
rename to tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.0.dump
new file mode 100644
index 0000000..5e197bd
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.0.dump
@@ -0,0 +1,127 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230], [timeUs=104727, position=13645]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 144086
+ sample count = 27
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 6456
+ channelCount = 2
+ sampleRate = 44000
+ initializationData:
+ data = length 42, hash 7249A1B8
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5415, hash 915DBC66
+ sample 1:
+ time = 104727
+ flags = 1
+ data = length 5529, hash EFD564F7
+ sample 2:
+ time = 209454
+ flags = 1
+ data = length 5480, hash ADA922FB
+ sample 3:
+ time = 314181
+ flags = 1
+ data = length 5290, hash 7BCEA5FC
+ sample 4:
+ time = 418909
+ flags = 1
+ data = length 5579, hash DBB36F37
+ sample 5:
+ time = 523636
+ flags = 1
+ data = length 5423, hash AB53F799
+ sample 6:
+ time = 628363
+ flags = 1
+ data = length 5583, hash 7243C284
+ sample 7:
+ time = 733090
+ flags = 1
+ data = length 5547, hash 9DA9C99E
+ sample 8:
+ time = 837818
+ flags = 1
+ data = length 5414, hash 90768345
+ sample 9:
+ time = 942545
+ flags = 1
+ data = length 5531, hash 1CD2FF67
+ sample 10:
+ time = 1047272
+ flags = 1
+ data = length 5870, hash A9A5CAEE
+ sample 11:
+ time = 1152000
+ flags = 1
+ data = length 5667, hash 875566A1
+ sample 12:
+ time = 1256727
+ flags = 1
+ data = length 5614, hash 5573694C
+ sample 13:
+ time = 1361454
+ flags = 1
+ data = length 6456, hash 921F3DE7
+ sample 14:
+ time = 1466181
+ flags = 1
+ data = length 5817, hash EBECBD16
+ sample 15:
+ time = 1570909
+ flags = 1
+ data = length 5751, hash 4A7D4C6B
+ sample 16:
+ time = 1675636
+ flags = 1
+ data = length 5620, hash B78F8E8D
+ sample 17:
+ time = 1780363
+ flags = 1
+ data = length 5535, hash 8187C107
+ sample 18:
+ time = 1885090
+ flags = 1
+ data = length 5517, hash 79FF36CB
+ sample 19:
+ time = 1989818
+ flags = 1
+ data = length 5716, hash 349FC281
+ sample 20:
+ time = 2094545
+ flags = 1
+ data = length 5556, hash BE97B2CA
+ sample 21:
+ time = 2199272
+ flags = 1
+ data = length 5703, hash 531F9FE3
+ sample 22:
+ time = 2304000
+ flags = 1
+ data = length 5652, hash 1277485D
+ sample 23:
+ time = 2408727
+ flags = 1
+ data = length 5607, hash 14862CB6
+ sample 24:
+ time = 2513454
+ flags = 1
+ data = length 5829, hash FCAF2F1C
+ sample 25:
+ time = 2618181
+ flags = 1
+ data = length 2837, hash 10F1716E
+ sample 26:
+ time = 2722909
+ flags = 1
+ data = length 548, hash B46F603C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.1.dump
new file mode 100644
index 0000000..3845602
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.1.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230], [timeUs=104727, position=13645]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 100240
+ sample count = 19
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 6456
+ channelCount = 2
+ sampleRate = 44000
+ initializationData:
+ data = length 42, hash 7249A1B8
+ sample 0:
+ time = 837818
+ flags = 1
+ data = length 5414, hash 90768345
+ sample 1:
+ time = 942545
+ flags = 1
+ data = length 5531, hash 1CD2FF67
+ sample 2:
+ time = 1047272
+ flags = 1
+ data = length 5870, hash A9A5CAEE
+ sample 3:
+ time = 1152000
+ flags = 1
+ data = length 5667, hash 875566A1
+ sample 4:
+ time = 1256727
+ flags = 1
+ data = length 5614, hash 5573694C
+ sample 5:
+ time = 1361454
+ flags = 1
+ data = length 6456, hash 921F3DE7
+ sample 6:
+ time = 1466181
+ flags = 1
+ data = length 5817, hash EBECBD16
+ sample 7:
+ time = 1570909
+ flags = 1
+ data = length 5751, hash 4A7D4C6B
+ sample 8:
+ time = 1675636
+ flags = 1
+ data = length 5620, hash B78F8E8D
+ sample 9:
+ time = 1780363
+ flags = 1
+ data = length 5535, hash 8187C107
+ sample 10:
+ time = 1885090
+ flags = 1
+ data = length 5517, hash 79FF36CB
+ sample 11:
+ time = 1989818
+ flags = 1
+ data = length 5716, hash 349FC281
+ sample 12:
+ time = 2094545
+ flags = 1
+ data = length 5556, hash BE97B2CA
+ sample 13:
+ time = 2199272
+ flags = 1
+ data = length 5703, hash 531F9FE3
+ sample 14:
+ time = 2304000
+ flags = 1
+ data = length 5652, hash 1277485D
+ sample 15:
+ time = 2408727
+ flags = 1
+ data = length 5607, hash 14862CB6
+ sample 16:
+ time = 2513454
+ flags = 1
+ data = length 5829, hash FCAF2F1C
+ sample 17:
+ time = 2618181
+ flags = 1
+ data = length 2837, hash 10F1716E
+ sample 18:
+ time = 2722909
+ flags = 1
+ data = length 548, hash B46F603C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.2.dump
new file mode 100644
index 0000000..1f68fe1
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.2.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230], [timeUs=104727, position=13645]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 48500
+ sample count = 10
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 6456
+ channelCount = 2
+ sampleRate = 44000
+ initializationData:
+ data = length 42, hash 7249A1B8
+ sample 0:
+ time = 1780363
+ flags = 1
+ data = length 5535, hash 8187C107
+ sample 1:
+ time = 1885090
+ flags = 1
+ data = length 5517, hash 79FF36CB
+ sample 2:
+ time = 1989818
+ flags = 1
+ data = length 5716, hash 349FC281
+ sample 3:
+ time = 2094545
+ flags = 1
+ data = length 5556, hash BE97B2CA
+ sample 4:
+ time = 2199272
+ flags = 1
+ data = length 5703, hash 531F9FE3
+ sample 5:
+ time = 2304000
+ flags = 1
+ data = length 5652, hash 1277485D
+ sample 6:
+ time = 2408727
+ flags = 1
+ data = length 5607, hash 14862CB6
+ sample 7:
+ time = 2513454
+ flags = 1
+ data = length 5829, hash FCAF2F1C
+ sample 8:
+ time = 2618181
+ flags = 1
+ data = length 2837, hash 10F1716E
+ sample 9:
+ time = 2722909
+ flags = 1
+ data = length 548, hash B46F603C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.3.dump
new file mode 100644
index 0000000..334f59a
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.3.dump
@@ -0,0 +1,27 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230], [timeUs=104727, position=13645]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3385
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 6456
+ channelCount = 2
+ sampleRate = 44000
+ initializationData:
+ data = length 42, hash 7249A1B8
+ sample 0:
+ time = 2618181
+ flags = 1
+ data = length 2837, hash 10F1716E
+ sample 1:
+ time = 2722909
+ flags = 1
+ data = length 548, hash B46F603C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.unknown_length.dump
new file mode 100644
index 0000000..5e197bd
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_flac.unknown_length.dump
@@ -0,0 +1,127 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230], [timeUs=104727, position=13645]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 144086
+ sample count = 27
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 6456
+ channelCount = 2
+ sampleRate = 44000
+ initializationData:
+ data = length 42, hash 7249A1B8
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5415, hash 915DBC66
+ sample 1:
+ time = 104727
+ flags = 1
+ data = length 5529, hash EFD564F7
+ sample 2:
+ time = 209454
+ flags = 1
+ data = length 5480, hash ADA922FB
+ sample 3:
+ time = 314181
+ flags = 1
+ data = length 5290, hash 7BCEA5FC
+ sample 4:
+ time = 418909
+ flags = 1
+ data = length 5579, hash DBB36F37
+ sample 5:
+ time = 523636
+ flags = 1
+ data = length 5423, hash AB53F799
+ sample 6:
+ time = 628363
+ flags = 1
+ data = length 5583, hash 7243C284
+ sample 7:
+ time = 733090
+ flags = 1
+ data = length 5547, hash 9DA9C99E
+ sample 8:
+ time = 837818
+ flags = 1
+ data = length 5414, hash 90768345
+ sample 9:
+ time = 942545
+ flags = 1
+ data = length 5531, hash 1CD2FF67
+ sample 10:
+ time = 1047272
+ flags = 1
+ data = length 5870, hash A9A5CAEE
+ sample 11:
+ time = 1152000
+ flags = 1
+ data = length 5667, hash 875566A1
+ sample 12:
+ time = 1256727
+ flags = 1
+ data = length 5614, hash 5573694C
+ sample 13:
+ time = 1361454
+ flags = 1
+ data = length 6456, hash 921F3DE7
+ sample 14:
+ time = 1466181
+ flags = 1
+ data = length 5817, hash EBECBD16
+ sample 15:
+ time = 1570909
+ flags = 1
+ data = length 5751, hash 4A7D4C6B
+ sample 16:
+ time = 1675636
+ flags = 1
+ data = length 5620, hash B78F8E8D
+ sample 17:
+ time = 1780363
+ flags = 1
+ data = length 5535, hash 8187C107
+ sample 18:
+ time = 1885090
+ flags = 1
+ data = length 5517, hash 79FF36CB
+ sample 19:
+ time = 1989818
+ flags = 1
+ data = length 5716, hash 349FC281
+ sample 20:
+ time = 2094545
+ flags = 1
+ data = length 5556, hash BE97B2CA
+ sample 21:
+ time = 2199272
+ flags = 1
+ data = length 5703, hash 531F9FE3
+ sample 22:
+ time = 2304000
+ flags = 1
+ data = length 5652, hash 1277485D
+ sample 23:
+ time = 2408727
+ flags = 1
+ data = length 5607, hash 14862CB6
+ sample 24:
+ time = 2513454
+ flags = 1
+ data = length 5829, hash FCAF2F1C
+ sample 25:
+ time = 2618181
+ flags = 1
+ data = length 2837, hash 10F1716E
+ sample 26:
+ time = 2722909
+ flags = 1
+ data = length 548, hash B46F603C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.0.dump
new file mode 100644
index 0000000..7c72df4
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.0.dump
@@ -0,0 +1,128 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 482416
+ sample count = 27
+ format 0:
+ averageBitrate = 1408000
+ peakBitrate = 1408000
+ sampleMimeType = audio/raw
+ maxInputSize = 18432
+ channelCount = 2
+ sampleRate = 44000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18432, hash A5825483
+ sample 1:
+ time = 104727
+ flags = 1
+ data = length 18432, hash 98A0A8C9
+ sample 2:
+ time = 209454
+ flags = 1
+ data = length 18432, hash 608A711B
+ sample 3:
+ time = 314181
+ flags = 1
+ data = length 18432, hash 1A9BCD1C
+ sample 4:
+ time = 418909
+ flags = 1
+ data = length 18432, hash 36E34B7F
+ sample 5:
+ time = 523636
+ flags = 1
+ data = length 18432, hash 29A75C1F
+ sample 6:
+ time = 628363
+ flags = 1
+ data = length 18432, hash 9E3A2940
+ sample 7:
+ time = 733090
+ flags = 1
+ data = length 18432, hash A7DF91EC
+ sample 8:
+ time = 837818
+ flags = 1
+ data = length 18432, hash 2B548030
+ sample 9:
+ time = 942545
+ flags = 1
+ data = length 18432, hash F905C735
+ sample 10:
+ time = 1047272
+ flags = 1
+ data = length 18432, hash D157292A
+ sample 11:
+ time = 1152000
+ flags = 1
+ data = length 18432, hash 1F721B83
+ sample 12:
+ time = 1256727
+ flags = 1
+ data = length 18432, hash AD520766
+ sample 13:
+ time = 1361454
+ flags = 1
+ data = length 18432, hash 7C6777AC
+ sample 14:
+ time = 1466181
+ flags = 1
+ data = length 18432, hash CC5E4807
+ sample 15:
+ time = 1570909
+ flags = 1
+ data = length 18432, hash 5542687C
+ sample 16:
+ time = 1675636
+ flags = 1
+ data = length 18432, hash 5C3E9A66
+ sample 17:
+ time = 1780363
+ flags = 1
+ data = length 18432, hash E868F171
+ sample 18:
+ time = 1885090
+ flags = 1
+ data = length 18432, hash 849820FB
+ sample 19:
+ time = 1989818
+ flags = 1
+ data = length 18432, hash 7C0F587C
+ sample 20:
+ time = 2094545
+ flags = 1
+ data = length 18432, hash C062C499
+ sample 21:
+ time = 2199272
+ flags = 1
+ data = length 18432, hash 3A5DE9D0
+ sample 22:
+ time = 2304000
+ flags = 1
+ data = length 18432, hash ACADCDDB
+ sample 23:
+ time = 2408727
+ flags = 1
+ data = length 18432, hash EEC815A8
+ sample 24:
+ time = 2513454
+ flags = 1
+ data = length 18432, hash 29FC8C0B
+ sample 25:
+ time = 2618181
+ flags = 1
+ data = length 18432, hash 1C5969B5
+ sample 26:
+ time = 2722909
+ flags = 1
+ data = length 3184, hash 1DB009F7
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.1.dump
new file mode 100644
index 0000000..7da240b
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.1.dump
@@ -0,0 +1,96 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 334960
+ sample count = 19
+ format 0:
+ averageBitrate = 1408000
+ peakBitrate = 1408000
+ sampleMimeType = audio/raw
+ maxInputSize = 18432
+ channelCount = 2
+ sampleRate = 44000
+ pcmEncoding = 2
+ sample 0:
+ time = 837818
+ flags = 1
+ data = length 18432, hash 2B548030
+ sample 1:
+ time = 942545
+ flags = 1
+ data = length 18432, hash F905C735
+ sample 2:
+ time = 1047272
+ flags = 1
+ data = length 18432, hash D157292A
+ sample 3:
+ time = 1152000
+ flags = 1
+ data = length 18432, hash 1F721B83
+ sample 4:
+ time = 1256727
+ flags = 1
+ data = length 18432, hash AD520766
+ sample 5:
+ time = 1361454
+ flags = 1
+ data = length 18432, hash 7C6777AC
+ sample 6:
+ time = 1466181
+ flags = 1
+ data = length 18432, hash CC5E4807
+ sample 7:
+ time = 1570909
+ flags = 1
+ data = length 18432, hash 5542687C
+ sample 8:
+ time = 1675636
+ flags = 1
+ data = length 18432, hash 5C3E9A66
+ sample 9:
+ time = 1780363
+ flags = 1
+ data = length 18432, hash E868F171
+ sample 10:
+ time = 1885090
+ flags = 1
+ data = length 18432, hash 849820FB
+ sample 11:
+ time = 1989818
+ flags = 1
+ data = length 18432, hash 7C0F587C
+ sample 12:
+ time = 2094545
+ flags = 1
+ data = length 18432, hash C062C499
+ sample 13:
+ time = 2199272
+ flags = 1
+ data = length 18432, hash 3A5DE9D0
+ sample 14:
+ time = 2304000
+ flags = 1
+ data = length 18432, hash ACADCDDB
+ sample 15:
+ time = 2408727
+ flags = 1
+ data = length 18432, hash EEC815A8
+ sample 16:
+ time = 2513454
+ flags = 1
+ data = length 18432, hash 29FC8C0B
+ sample 17:
+ time = 2618181
+ flags = 1
+ data = length 18432, hash 1C5969B5
+ sample 18:
+ time = 2722909
+ flags = 1
+ data = length 3184, hash 1DB009F7
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.2.dump
new file mode 100644
index 0000000..3943c77
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.2.dump
@@ -0,0 +1,60 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 169072
+ sample count = 10
+ format 0:
+ averageBitrate = 1408000
+ peakBitrate = 1408000
+ sampleMimeType = audio/raw
+ maxInputSize = 18432
+ channelCount = 2
+ sampleRate = 44000
+ pcmEncoding = 2
+ sample 0:
+ time = 1780363
+ flags = 1
+ data = length 18432, hash E868F171
+ sample 1:
+ time = 1885090
+ flags = 1
+ data = length 18432, hash 849820FB
+ sample 2:
+ time = 1989818
+ flags = 1
+ data = length 18432, hash 7C0F587C
+ sample 3:
+ time = 2094545
+ flags = 1
+ data = length 18432, hash C062C499
+ sample 4:
+ time = 2199272
+ flags = 1
+ data = length 18432, hash 3A5DE9D0
+ sample 5:
+ time = 2304000
+ flags = 1
+ data = length 18432, hash ACADCDDB
+ sample 6:
+ time = 2408727
+ flags = 1
+ data = length 18432, hash EEC815A8
+ sample 7:
+ time = 2513454
+ flags = 1
+ data = length 18432, hash 29FC8C0B
+ sample 8:
+ time = 2618181
+ flags = 1
+ data = length 18432, hash 1C5969B5
+ sample 9:
+ time = 2722909
+ flags = 1
+ data = length 3184, hash 1DB009F7
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.3.dump
new file mode 100644
index 0000000..1823141
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 21616
+ sample count = 2
+ format 0:
+ averageBitrate = 1408000
+ peakBitrate = 1408000
+ sampleMimeType = audio/raw
+ maxInputSize = 18432
+ channelCount = 2
+ sampleRate = 44000
+ pcmEncoding = 2
+ sample 0:
+ time = 2618181
+ flags = 1
+ data = length 18432, hash 1C5969B5
+ sample 1:
+ time = 2722909
+ flags = 1
+ data = length 3184, hash 1DB009F7
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.unknown_length.dump
new file mode 100644
index 0000000..7c72df4
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_uncommon_sample_rate_raw.unknown_length.dump
@@ -0,0 +1,128 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8230]]
+ getPosition(1) = [[timeUs=0, position=8230]]
+ getPosition(1370500) = [[timeUs=1361454, position=80172], [timeUs=1466181, position=86628]]
+ getPosition(2741000) = [[timeUs=2618181, position=148931]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 482416
+ sample count = 27
+ format 0:
+ averageBitrate = 1408000
+ peakBitrate = 1408000
+ sampleMimeType = audio/raw
+ maxInputSize = 18432
+ channelCount = 2
+ sampleRate = 44000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18432, hash A5825483
+ sample 1:
+ time = 104727
+ flags = 1
+ data = length 18432, hash 98A0A8C9
+ sample 2:
+ time = 209454
+ flags = 1
+ data = length 18432, hash 608A711B
+ sample 3:
+ time = 314181
+ flags = 1
+ data = length 18432, hash 1A9BCD1C
+ sample 4:
+ time = 418909
+ flags = 1
+ data = length 18432, hash 36E34B7F
+ sample 5:
+ time = 523636
+ flags = 1
+ data = length 18432, hash 29A75C1F
+ sample 6:
+ time = 628363
+ flags = 1
+ data = length 18432, hash 9E3A2940
+ sample 7:
+ time = 733090
+ flags = 1
+ data = length 18432, hash A7DF91EC
+ sample 8:
+ time = 837818
+ flags = 1
+ data = length 18432, hash 2B548030
+ sample 9:
+ time = 942545
+ flags = 1
+ data = length 18432, hash F905C735
+ sample 10:
+ time = 1047272
+ flags = 1
+ data = length 18432, hash D157292A
+ sample 11:
+ time = 1152000
+ flags = 1
+ data = length 18432, hash 1F721B83
+ sample 12:
+ time = 1256727
+ flags = 1
+ data = length 18432, hash AD520766
+ sample 13:
+ time = 1361454
+ flags = 1
+ data = length 18432, hash 7C6777AC
+ sample 14:
+ time = 1466181
+ flags = 1
+ data = length 18432, hash CC5E4807
+ sample 15:
+ time = 1570909
+ flags = 1
+ data = length 18432, hash 5542687C
+ sample 16:
+ time = 1675636
+ flags = 1
+ data = length 18432, hash 5C3E9A66
+ sample 17:
+ time = 1780363
+ flags = 1
+ data = length 18432, hash E868F171
+ sample 18:
+ time = 1885090
+ flags = 1
+ data = length 18432, hash 849820FB
+ sample 19:
+ time = 1989818
+ flags = 1
+ data = length 18432, hash 7C0F587C
+ sample 20:
+ time = 2094545
+ flags = 1
+ data = length 18432, hash C062C499
+ sample 21:
+ time = 2199272
+ flags = 1
+ data = length 18432, hash 3A5DE9D0
+ sample 22:
+ time = 2304000
+ flags = 1
+ data = length 18432, hash ACADCDDB
+ sample 23:
+ time = 2408727
+ flags = 1
+ data = length 18432, hash EEC815A8
+ sample 24:
+ time = 2513454
+ flags = 1
+ data = length 18432, hash 29FC8C0B
+ sample 25:
+ time = 2618181
+ flags = 1
+ data = length 18432, hash 1C5969B5
+ sample 26:
+ time = 2722909
+ flags = 1
+ data = length 3184, hash 1DB009F7
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac b/tree/testdata/src/test/assets/flac/bear_with_id3.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_with_id3_disabled.flac
rename to tree/testdata/src/test/assets/flac/bear_with_id3.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.0.dump
new file mode 100644
index 0000000..3b3f661
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.0.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.1.dump
new file mode 100644
index 0000000..57501d7
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.1.dump
@@ -0,0 +1,111 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.2.dump
new file mode 100644
index 0000000..c931549
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.2.dump
@@ -0,0 +1,67 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.3.dump
new file mode 100644
index 0000000..1344af1
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.3.dump
@@ -0,0 +1,27 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.unknown_length.dump
new file mode 100644
index 0000000..3b3f661
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_flac.unknown_length.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.0.dump
new file mode 100644
index 0000000..fcfa917
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.1.dump
new file mode 100644
index 0000000..ad1c171
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.2.dump
new file mode 100644
index 0000000..e5f625e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.3.dump
new file mode 100644
index 0000000..1625462
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.unknown_length.dump
new file mode 100644
index 0000000..fcfa917
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_disabled_raw.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.0.dump
new file mode 100644
index 0000000..d2a8d6a
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.1.dump
new file mode 100644
index 0000000..250d1ad
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.2.dump
new file mode 100644
index 0000000..e5057cf
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.3.dump
new file mode 100644
index 0000000..afaead1
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.unknown_length.dump
new file mode 100644
index 0000000..d2a8d6a
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_flac.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284], [timeUs=85333, position=60314]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.0.dump
new file mode 100644
index 0000000..ca9f1a7
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.0.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.1.dump
new file mode 100644
index 0000000..36314d9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.1.dump
@@ -0,0 +1,113 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.2.dump
new file mode 100644
index 0000000..0e8cc73
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.2.dump
@@ -0,0 +1,69 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.3.dump
new file mode 100644
index 0000000..8ef6f9c
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.3.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.unknown_length.dump
new file mode 100644
index 0000000..ca9f1a7
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_id3_enabled_raw.unknown_length.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=55284]]
+ getPosition(1) = [[timeUs=0, position=55284]]
+ getPosition(1370500) = [[timeUs=1365333, position=137229], [timeUs=1450666, position=143005]]
+ getPosition(2741000) = [[timeUs=2645333, position=215886]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_picture.flac b/tree/testdata/src/test/assets/flac/bear_with_picture.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_with_picture.flac
rename to tree/testdata/src/test/assets/flac/bear_with_picture.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.0.dump
new file mode 100644
index 0000000..57c1816
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868], [timeUs=85333, position=44898]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.1.dump
new file mode 100644
index 0000000..90da891
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868], [timeUs=85333, position=44898]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.2.dump
new file mode 100644
index 0000000..f916615
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868], [timeUs=85333, position=44898]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.3.dump
new file mode 100644
index 0000000..a3292df
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868], [timeUs=85333, position=44898]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.unknown_length.dump
new file mode 100644
index 0000000..57c1816
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_flac.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868], [timeUs=85333, position=44898]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.0.dump
new file mode 100644
index 0000000..0ca949e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.0.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.1.dump
new file mode 100644
index 0000000..075fcec
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.1.dump
@@ -0,0 +1,113 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.2.dump
new file mode 100644
index 0000000..a49beee
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.2.dump
@@ -0,0 +1,69 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.3.dump
new file mode 100644
index 0000000..22330e4
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.3.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_picture_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.unknown_length.dump
new file mode 100644
index 0000000..0ca949e
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_picture_raw.unknown_length.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=39868]]
+ getPosition(1) = [[timeUs=0, position=39868]]
+ getPosition(1370500) = [[timeUs=1365333, position=121813], [timeUs=1450666, position=127589]]
+ getPosition(2741000) = [[timeUs=2645333, position=200470]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[Picture: mimeType=image/png, description=]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments.flac
similarity index 100%
rename from tree/library/extractor/src/test/assets/flac/bear_with_vorbis_comments.flac
rename to tree/testdata/src/test/assets/flac/bear_with_vorbis_comments.flac
Binary files differ
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.0.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.0.dump
new file mode 100644
index 0000000..d5cdf8c
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.0.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.1.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.1.dump
new file mode 100644
index 0000000..b3d7f93
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.1.dump
@@ -0,0 +1,112 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.2.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.2.dump
new file mode 100644
index 0000000..58c8fb3
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.2.dump
@@ -0,0 +1,68 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.3.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.3.dump
new file mode 100644
index 0000000..7f2a7eb
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3829
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.unknown_length.dump
new file mode 100644
index 0000000..d5cdf8c
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_flac.unknown_length.dump
@@ -0,0 +1,152 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880], [timeUs=85333, position=13910]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.0.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.0.dump
new file mode 100644
index 0000000..d6faa10
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.0.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.1.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.1.dump
new file mode 100644
index 0000000..7e2bac3
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.1.dump
@@ -0,0 +1,113 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 362432
+ sample count = 23
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.2.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.2.dump
new file mode 100644
index 0000000..642a7a9
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.2.dump
@@ -0,0 +1,69 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 182208
+ sample count = 12
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.3.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.3.dump
new file mode 100644
index 0000000..262c5dd
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.3.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 18368
+ sample count = 2
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ sample 0:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 1:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.unknown_length.dump b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.unknown_length.dump
new file mode 100644
index 0000000..d6faa10
--- /dev/null
+++ b/tree/testdata/src/test/assets/flac/bear_with_vorbis_comments_raw.unknown_length.dump
@@ -0,0 +1,153 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8880]]
+ getPosition(1) = [[timeUs=0, position=8880]]
+ getPosition(1370500) = [[timeUs=1365333, position=90825], [timeUs=1450666, position=96601]]
+ getPosition(2741000) = [[timeUs=2645333, position=169482]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 526272
+ sample count = 33
+ format 0:
+ averageBitrate = 1536000
+ peakBitrate = 1536000
+ sampleMimeType = audio/raw
+ maxInputSize = 16384
+ channelCount = 2
+ sampleRate = 48000
+ pcmEncoding = 2
+ metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 16384, hash 61D2C5C2
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 16384, hash E6D7F214
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 16384, hash 59BF0D5D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 16384, hash 3625F468
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 16384, hash F66A323
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 16384, hash CDBAE629
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 16384, hash 536F3A91
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 16384, hash D4F35C9C
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 16384, hash EE04CEBF
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 16384, hash 647E2A67
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 16384, hash 31583F2C
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 16384, hash E433A93D
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 16384, hash 5E1C7051
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 16384, hash 43E6E358
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 16384, hash 5DC1B256
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 16384, hash 3D9D95CF
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 16384, hash 2A5BD2C0
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 16384, hash 93E25061
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 16384, hash B81793D8
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 16384, hash 1A3BD49F
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 16384, hash FB672FF1
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 16384, hash 48AB8B45
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 16384, hash 13C9640A
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 16384, hash 499E4A0B
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 16384, hash F9A783E6
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 16384, hash D2B77598
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 16384, hash CE5B826C
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 16384, hash E99EE956
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 16384, hash F2DB1486
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 16384, hash 1636EAB
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 16384, hash 23457C08
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 16384, hash 30EB8381
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 1984, hash 59CFDE1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/flv/sample.flv b/tree/testdata/src/test/assets/flv/sample.flv
similarity index 100%
rename from tree/library/extractor/src/test/assets/flv/sample.flv
rename to tree/testdata/src/test/assets/flv/sample.flv
Binary files differ
diff --git a/tree/testdata/src/test/assets/flv/sample.flv.0.dump b/tree/testdata/src/test/assets/flv/sample.flv.0.dump
new file mode 100644
index 0000000..8bf765b
--- /dev/null
+++ b/tree/testdata/src/test/assets/flv/sample.flv.0.dump
@@ -0,0 +1,326 @@
+seekMap:
+ isSeekable = false
+ duration = 1136000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 8:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 112000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 135000
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 158000
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 181000
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 205000
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 228000
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 251000
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 274000
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 298000
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 321000
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 344000
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 367000
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 390000
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 414000
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 437000
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 460000
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 483000
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 507000
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 530000
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 553000
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 576000
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 599000
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 623000
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 646000
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 669000
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 692000
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 716000
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 739000
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 762000
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 785000
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 808000
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 832000
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 855000
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 878000
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 901000
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 925000
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 948000
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 971000
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 994000
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 1017000
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 1041000
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 1064000
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1087000
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1110000
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1134000
+ flags = 1
+ data = length 6, hash 31B22286
+track 9:
+ total output bytes = 89502
+ sample count = 30
+ format 0:
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 67000
+ flags = 1
+ data = length 36477, hash F0F36CFE
+ sample 1:
+ time = 134000
+ flags = 0
+ data = length 5341, hash 40B85E2
+ sample 2:
+ time = 100000
+ flags = 0
+ data = length 596, hash 357B4D92
+ sample 3:
+ time = 267000
+ flags = 0
+ data = length 7704, hash A39EDA06
+ sample 4:
+ time = 200000
+ flags = 0
+ data = length 989, hash 2813C72D
+ sample 5:
+ time = 167000
+ flags = 0
+ data = length 721, hash C50D1C73
+ sample 6:
+ time = 234000
+ flags = 0
+ data = length 519, hash 65FE1911
+ sample 7:
+ time = 400000
+ flags = 0
+ data = length 6160, hash E1CAC0EC
+ sample 8:
+ time = 334000
+ flags = 0
+ data = length 953, hash 7160C661
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 620, hash 7A7AE07C
+ sample 10:
+ time = 367000
+ flags = 0
+ data = length 405, hash 5CC7F4E7
+ sample 11:
+ time = 500000
+ flags = 0
+ data = length 4852, hash 9DB6979D
+ sample 12:
+ time = 467000
+ flags = 0
+ data = length 547, hash E31A6979
+ sample 13:
+ time = 434000
+ flags = 0
+ data = length 570, hash FEC40D00
+ sample 14:
+ time = 634000
+ flags = 0
+ data = length 5525, hash 7C478F7E
+ sample 15:
+ time = 567000
+ flags = 0
+ data = length 1082, hash DA07059A
+ sample 16:
+ time = 534000
+ flags = 0
+ data = length 807, hash 93478E6B
+ sample 17:
+ time = 600000
+ flags = 0
+ data = length 744, hash 9A8E6026
+ sample 18:
+ time = 767000
+ flags = 0
+ data = length 4732, hash C73B23C0
+ sample 19:
+ time = 700000
+ flags = 0
+ data = length 1004, hash 8A19A228
+ sample 20:
+ time = 667000
+ flags = 0
+ data = length 794, hash 8126022C
+ sample 21:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 22:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 23:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 25:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 26:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 27:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 28:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 29:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/flv/sample.flv.unknown_length.dump b/tree/testdata/src/test/assets/flv/sample.flv.unknown_length.dump
new file mode 100644
index 0000000..8bf765b
--- /dev/null
+++ b/tree/testdata/src/test/assets/flv/sample.flv.unknown_length.dump
@@ -0,0 +1,326 @@
+seekMap:
+ isSeekable = false
+ duration = 1136000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 8:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 112000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 135000
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 158000
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 181000
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 205000
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 228000
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 251000
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 274000
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 298000
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 321000
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 344000
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 367000
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 390000
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 414000
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 437000
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 460000
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 483000
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 507000
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 530000
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 553000
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 576000
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 599000
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 623000
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 646000
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 669000
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 692000
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 716000
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 739000
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 762000
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 785000
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 808000
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 832000
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 855000
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 878000
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 901000
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 925000
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 948000
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 971000
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 994000
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 1017000
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 1041000
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 1064000
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1087000
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1110000
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1134000
+ flags = 1
+ data = length 6, hash 31B22286
+track 9:
+ total output bytes = 89502
+ sample count = 30
+ format 0:
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 67000
+ flags = 1
+ data = length 36477, hash F0F36CFE
+ sample 1:
+ time = 134000
+ flags = 0
+ data = length 5341, hash 40B85E2
+ sample 2:
+ time = 100000
+ flags = 0
+ data = length 596, hash 357B4D92
+ sample 3:
+ time = 267000
+ flags = 0
+ data = length 7704, hash A39EDA06
+ sample 4:
+ time = 200000
+ flags = 0
+ data = length 989, hash 2813C72D
+ sample 5:
+ time = 167000
+ flags = 0
+ data = length 721, hash C50D1C73
+ sample 6:
+ time = 234000
+ flags = 0
+ data = length 519, hash 65FE1911
+ sample 7:
+ time = 400000
+ flags = 0
+ data = length 6160, hash E1CAC0EC
+ sample 8:
+ time = 334000
+ flags = 0
+ data = length 953, hash 7160C661
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 620, hash 7A7AE07C
+ sample 10:
+ time = 367000
+ flags = 0
+ data = length 405, hash 5CC7F4E7
+ sample 11:
+ time = 500000
+ flags = 0
+ data = length 4852, hash 9DB6979D
+ sample 12:
+ time = 467000
+ flags = 0
+ data = length 547, hash E31A6979
+ sample 13:
+ time = 434000
+ flags = 0
+ data = length 570, hash FEC40D00
+ sample 14:
+ time = 634000
+ flags = 0
+ data = length 5525, hash 7C478F7E
+ sample 15:
+ time = 567000
+ flags = 0
+ data = length 1082, hash DA07059A
+ sample 16:
+ time = 534000
+ flags = 0
+ data = length 807, hash 93478E6B
+ sample 17:
+ time = 600000
+ flags = 0
+ data = length 744, hash 9A8E6026
+ sample 18:
+ time = 767000
+ flags = 0
+ data = length 4732, hash C73B23C0
+ sample 19:
+ time = 700000
+ flags = 0
+ data = length 1004, hash 8A19A228
+ sample 20:
+ time = 667000
+ flags = 0
+ data = length 794, hash 8126022C
+ sample 21:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 22:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 23:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 25:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 26:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 27:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 28:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 29:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/id3/apic.id3 b/tree/testdata/src/test/assets/id3/apic.id3
similarity index 100%
rename from tree/library/extractor/src/test/assets/id3/apic.id3
rename to tree/testdata/src/test/assets/id3/apic.id3
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/id3/comm_apic.id3 b/tree/testdata/src/test/assets/id3/comm_apic.id3
similarity index 100%
rename from tree/library/extractor/src/test/assets/id3/comm_apic.id3
rename to tree/testdata/src/test/assets/id3/comm_apic.id3
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear-flac-16bit.mka b/tree/testdata/src/test/assets/mka/bear-flac-16bit.mka
similarity index 100%
rename from tree/extensions/flac/src/androidTest/assets/bear-flac-16bit.mka
rename to tree/testdata/src/test/assets/mka/bear-flac-16bit.mka
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear-flac-16bit.mka.audiosink.dump b/tree/testdata/src/test/assets/mka/bear-flac-16bit.mka.audiosink.dump
similarity index 100%
rename from tree/extensions/flac/src/androidTest/assets/bear-flac-16bit.mka.audiosink.dump
rename to tree/testdata/src/test/assets/mka/bear-flac-16bit.mka.audiosink.dump
diff --git a/tree/extensions/flac/src/androidTest/assets/bear-flac-24bit.mka b/tree/testdata/src/test/assets/mka/bear-flac-24bit.mka
similarity index 100%
rename from tree/extensions/flac/src/androidTest/assets/bear-flac-24bit.mka
rename to tree/testdata/src/test/assets/mka/bear-flac-24bit.mka
Binary files differ
diff --git a/tree/extensions/flac/src/androidTest/assets/bear-flac-24bit.mka.audiosink.dump b/tree/testdata/src/test/assets/mka/bear-flac-24bit.mka.audiosink.dump
similarity index 100%
rename from tree/extensions/flac/src/androidTest/assets/bear-flac-24bit.mka.audiosink.dump
rename to tree/testdata/src/test/assets/mka/bear-flac-24bit.mka.audiosink.dump
diff --git a/tree/testdata/src/test/assets/mka/bear-opus-negative-gain.mka b/tree/testdata/src/test/assets/mka/bear-opus-negative-gain.mka
new file mode 100644
index 0000000..3ee21ad
--- /dev/null
+++ b/tree/testdata/src/test/assets/mka/bear-opus-negative-gain.mka
Binary files differ
diff --git a/tree/extensions/opus/src/androidTest/assets/bear-opus.webm b/tree/testdata/src/test/assets/mka/bear-opus.mka
similarity index 100%
rename from tree/extensions/opus/src/androidTest/assets/bear-opus.webm
rename to tree/testdata/src/test/assets/mka/bear-opus.mka
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/mkv/full_blocks.mkv b/tree/testdata/src/test/assets/mkv/full_blocks.mkv
similarity index 100%
rename from tree/library/extractor/src/test/assets/mkv/full_blocks.mkv
rename to tree/testdata/src/test/assets/mkv/full_blocks.mkv
Binary files differ
diff --git a/tree/testdata/src/test/assets/mkv/full_blocks.mkv.0.dump b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.0.dump
new file mode 100644
index 0000000..3afc87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.0.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 8901000
+ getPosition(0) = [[timeUs=0, position=5401]]
+ getPosition(1) = [[timeUs=0, position=5401], [timeUs=2345000, position=5401]]
+ getPosition(4450500) = [[timeUs=2345000, position=5401], [timeUs=4567000, position=5401]]
+ getPosition(8901000) = [[timeUs=4567000, position=5401]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 213
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = application/x-subrip
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 59, hash 1AD38625
+ sample 1:
+ time = 2345000
+ flags = 1
+ data = length 95, hash F331C282
+ sample 2:
+ time = 4567000
+ flags = 1
+ data = length 59, hash F8CD7C60
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/full_blocks.mkv.1.dump b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.1.dump
new file mode 100644
index 0000000..3afc87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.1.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 8901000
+ getPosition(0) = [[timeUs=0, position=5401]]
+ getPosition(1) = [[timeUs=0, position=5401], [timeUs=2345000, position=5401]]
+ getPosition(4450500) = [[timeUs=2345000, position=5401], [timeUs=4567000, position=5401]]
+ getPosition(8901000) = [[timeUs=4567000, position=5401]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 213
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = application/x-subrip
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 59, hash 1AD38625
+ sample 1:
+ time = 2345000
+ flags = 1
+ data = length 95, hash F331C282
+ sample 2:
+ time = 4567000
+ flags = 1
+ data = length 59, hash F8CD7C60
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/full_blocks.mkv.2.dump b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.2.dump
new file mode 100644
index 0000000..3afc87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.2.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 8901000
+ getPosition(0) = [[timeUs=0, position=5401]]
+ getPosition(1) = [[timeUs=0, position=5401], [timeUs=2345000, position=5401]]
+ getPosition(4450500) = [[timeUs=2345000, position=5401], [timeUs=4567000, position=5401]]
+ getPosition(8901000) = [[timeUs=4567000, position=5401]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 213
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = application/x-subrip
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 59, hash 1AD38625
+ sample 1:
+ time = 2345000
+ flags = 1
+ data = length 95, hash F331C282
+ sample 2:
+ time = 4567000
+ flags = 1
+ data = length 59, hash F8CD7C60
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/full_blocks.mkv.3.dump b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.3.dump
new file mode 100644
index 0000000..3afc87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.3.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 8901000
+ getPosition(0) = [[timeUs=0, position=5401]]
+ getPosition(1) = [[timeUs=0, position=5401], [timeUs=2345000, position=5401]]
+ getPosition(4450500) = [[timeUs=2345000, position=5401], [timeUs=4567000, position=5401]]
+ getPosition(8901000) = [[timeUs=4567000, position=5401]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 213
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = application/x-subrip
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 59, hash 1AD38625
+ sample 1:
+ time = 2345000
+ flags = 1
+ data = length 95, hash F331C282
+ sample 2:
+ time = 4567000
+ flags = 1
+ data = length 59, hash F8CD7C60
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/full_blocks.mkv.unknown_length.dump b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.unknown_length.dump
new file mode 100644
index 0000000..3afc87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/full_blocks.mkv.unknown_length.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 8901000
+ getPosition(0) = [[timeUs=0, position=5401]]
+ getPosition(1) = [[timeUs=0, position=5401], [timeUs=2345000, position=5401]]
+ getPosition(4450500) = [[timeUs=2345000, position=5401], [timeUs=4567000, position=5401]]
+ getPosition(8901000) = [[timeUs=4567000, position=5401]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 213
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = application/x-subrip
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 59, hash 1AD38625
+ sample 1:
+ time = 2345000
+ flags = 1
+ data = length 95, hash F331C282
+ sample 2:
+ time = 4567000
+ flags = 1
+ data = length 59, hash F8CD7C60
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/sample.mkv b/tree/testdata/src/test/assets/mkv/sample.mkv
similarity index 100%
rename from tree/library/extractor/src/test/assets/mkv/sample.mkv
rename to tree/testdata/src/test/assets/mkv/sample.mkv
Binary files differ
diff --git a/tree/testdata/src/test/assets/mkv/sample.mkv.0.dump b/tree/testdata/src/test/assets/mkv/sample.mkv.0.dump
new file mode 100644
index 0000000..bb8c063
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/sample.mkv.0.dump
@@ -0,0 +1,268 @@
+seekMap:
+ isSeekable = true
+ duration = 1104000
+ getPosition(0) = [[timeUs=67000, position=5576]]
+ getPosition(1) = [[timeUs=67000, position=5576]]
+ getPosition(552000) = [[timeUs=547000, position=77334], [timeUs=567000, position=84155]]
+ getPosition(1104000) = [[timeUs=1035000, position=106570]]
+numberOfTracks = 2
+track 1:
+ total output bytes = 89502
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ selectionFlags = 1
+ language = und
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 67000
+ flags = 1
+ data = length 36477, hash F0F36CFE
+ sample 1:
+ time = 134000
+ flags = 0
+ data = length 5341, hash 40B85E2
+ sample 2:
+ time = 100000
+ flags = 0
+ data = length 596, hash 357B4D92
+ sample 3:
+ time = 267000
+ flags = 0
+ data = length 7704, hash A39EDA06
+ sample 4:
+ time = 200000
+ flags = 0
+ data = length 989, hash 2813C72D
+ sample 5:
+ time = 167000
+ flags = 0
+ data = length 721, hash C50D1C73
+ sample 6:
+ time = 234000
+ flags = 0
+ data = length 519, hash 65FE1911
+ sample 7:
+ time = 400000
+ flags = 0
+ data = length 6160, hash E1CAC0EC
+ sample 8:
+ time = 334000
+ flags = 0
+ data = length 953, hash 7160C661
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 620, hash 7A7AE07C
+ sample 10:
+ time = 367000
+ flags = 0
+ data = length 405, hash 5CC7F4E7
+ sample 11:
+ time = 500000
+ flags = 0
+ data = length 4852, hash 9DB6979D
+ sample 12:
+ time = 467000
+ flags = 0
+ data = length 547, hash E31A6979
+ sample 13:
+ time = 434000
+ flags = 0
+ data = length 570, hash FEC40D00
+ sample 14:
+ time = 634000
+ flags = 0
+ data = length 5525, hash 7C478F7E
+ sample 15:
+ time = 567000
+ flags = 0
+ data = length 1082, hash DA07059A
+ sample 16:
+ time = 534000
+ flags = 0
+ data = length 807, hash 93478E6B
+ sample 17:
+ time = 600000
+ flags = 0
+ data = length 744, hash 9A8E6026
+ sample 18:
+ time = 767000
+ flags = 0
+ data = length 4732, hash C73B23C0
+ sample 19:
+ time = 700000
+ flags = 0
+ data = length 1004, hash 8A19A228
+ sample 20:
+ time = 667000
+ flags = 0
+ data = length 794, hash 8126022C
+ sample 21:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 22:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 23:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 25:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 26:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 27:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 28:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 29:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+track 2:
+ total output bytes = 12120
+ sample count = 29
+ format 0:
+ id = 2
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 129000
+ flags = 1
+ data = length 416, hash 211F2286
+ sample 1:
+ time = 164000
+ flags = 1
+ data = length 418, hash 77425A86
+ sample 2:
+ time = 198829
+ flags = 1
+ data = length 418, hash A0FE5CA1
+ sample 3:
+ time = 233000
+ flags = 1
+ data = length 418, hash 2309B066
+ sample 4:
+ time = 268000
+ flags = 1
+ data = length 418, hash 928A653B
+ sample 5:
+ time = 303000
+ flags = 1
+ data = length 418, hash 3422F0CB
+ sample 6:
+ time = 337829
+ flags = 1
+ data = length 418, hash EFF43D5B
+ sample 7:
+ time = 373000
+ flags = 1
+ data = length 418, hash FC8093C7
+ sample 8:
+ time = 408000
+ flags = 1
+ data = length 418, hash CCC08A16
+ sample 9:
+ time = 443000
+ flags = 1
+ data = length 418, hash 2A6EE863
+ sample 10:
+ time = 477829
+ flags = 1
+ data = length 418, hash D69A9251
+ sample 11:
+ time = 512000
+ flags = 1
+ data = length 418, hash BCFB758D
+ sample 12:
+ time = 547000
+ flags = 1
+ data = length 418, hash 11B66799
+ sample 13:
+ time = 581829
+ flags = 1
+ data = length 418, hash C824D392
+ sample 14:
+ time = 617000
+ flags = 1
+ data = length 418, hash C167D872
+ sample 15:
+ time = 652000
+ flags = 1
+ data = length 418, hash 4221C855
+ sample 16:
+ time = 687000
+ flags = 1
+ data = length 418, hash 4D4FF934
+ sample 17:
+ time = 721829
+ flags = 1
+ data = length 418, hash 984AA025
+ sample 18:
+ time = 757000
+ flags = 1
+ data = length 418, hash BB788B46
+ sample 19:
+ time = 791000
+ flags = 1
+ data = length 418, hash 9EFBFD97
+ sample 20:
+ time = 826000
+ flags = 1
+ data = length 418, hash DF1A460C
+ sample 21:
+ time = 860829
+ flags = 1
+ data = length 418, hash 2BDB56A
+ sample 22:
+ time = 896000
+ flags = 1
+ data = length 418, hash CA230060
+ sample 23:
+ time = 931000
+ flags = 1
+ data = length 418, hash D2F19F41
+ sample 24:
+ time = 965000
+ flags = 1
+ data = length 418, hash AF392D79
+ sample 25:
+ time = 999829
+ flags = 1
+ data = length 418, hash C5D7F2A3
+ sample 26:
+ time = 1035000
+ flags = 1
+ data = length 418, hash 733A35AE
+ sample 27:
+ time = 1069829
+ flags = 1
+ data = length 418, hash DE46E5D3
+ sample 28:
+ time = 1104000
+ flags = 1
+ data = length 418, hash 56AB8D37
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/sample.mkv.1.dump b/tree/testdata/src/test/assets/mkv/sample.mkv.1.dump
new file mode 100644
index 0000000..348f66e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/sample.mkv.1.dump
@@ -0,0 +1,196 @@
+seekMap:
+ isSeekable = true
+ duration = 1104000
+ getPosition(0) = [[timeUs=67000, position=5576]]
+ getPosition(1) = [[timeUs=67000, position=5576]]
+ getPosition(552000) = [[timeUs=547000, position=77334], [timeUs=567000, position=84155]]
+ getPosition(1104000) = [[timeUs=1035000, position=106570]]
+numberOfTracks = 2
+track 1:
+ total output bytes = 29422
+ sample count = 20
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ selectionFlags = 1
+ language = und
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 367000
+ flags = 0
+ data = length 405, hash 5CC7F4E7
+ sample 1:
+ time = 500000
+ flags = 0
+ data = length 4852, hash 9DB6979D
+ sample 2:
+ time = 467000
+ flags = 0
+ data = length 547, hash E31A6979
+ sample 3:
+ time = 434000
+ flags = 0
+ data = length 570, hash FEC40D00
+ sample 4:
+ time = 634000
+ flags = 0
+ data = length 5525, hash 7C478F7E
+ sample 5:
+ time = 567000
+ flags = 0
+ data = length 1082, hash DA07059A
+ sample 6:
+ time = 534000
+ flags = 0
+ data = length 807, hash 93478E6B
+ sample 7:
+ time = 600000
+ flags = 0
+ data = length 744, hash 9A8E6026
+ sample 8:
+ time = 767000
+ flags = 0
+ data = length 4732, hash C73B23C0
+ sample 9:
+ time = 700000
+ flags = 0
+ data = length 1004, hash 8A19A228
+ sample 10:
+ time = 667000
+ flags = 0
+ data = length 794, hash 8126022C
+ sample 11:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 12:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 13:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 14:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 15:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 16:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 17:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 18:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 19:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+track 2:
+ total output bytes = 8778
+ sample count = 21
+ format 0:
+ id = 2
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 408000
+ flags = 1
+ data = length 418, hash CCC08A16
+ sample 1:
+ time = 443000
+ flags = 1
+ data = length 418, hash 2A6EE863
+ sample 2:
+ time = 477829
+ flags = 1
+ data = length 418, hash D69A9251
+ sample 3:
+ time = 512000
+ flags = 1
+ data = length 418, hash BCFB758D
+ sample 4:
+ time = 547000
+ flags = 1
+ data = length 418, hash 11B66799
+ sample 5:
+ time = 581829
+ flags = 1
+ data = length 418, hash C824D392
+ sample 6:
+ time = 617000
+ flags = 1
+ data = length 418, hash C167D872
+ sample 7:
+ time = 652000
+ flags = 1
+ data = length 418, hash 4221C855
+ sample 8:
+ time = 687000
+ flags = 1
+ data = length 418, hash 4D4FF934
+ sample 9:
+ time = 721829
+ flags = 1
+ data = length 418, hash 984AA025
+ sample 10:
+ time = 757000
+ flags = 1
+ data = length 418, hash BB788B46
+ sample 11:
+ time = 791000
+ flags = 1
+ data = length 418, hash 9EFBFD97
+ sample 12:
+ time = 826000
+ flags = 1
+ data = length 418, hash DF1A460C
+ sample 13:
+ time = 860829
+ flags = 1
+ data = length 418, hash 2BDB56A
+ sample 14:
+ time = 896000
+ flags = 1
+ data = length 418, hash CA230060
+ sample 15:
+ time = 931000
+ flags = 1
+ data = length 418, hash D2F19F41
+ sample 16:
+ time = 965000
+ flags = 1
+ data = length 418, hash AF392D79
+ sample 17:
+ time = 999829
+ flags = 1
+ data = length 418, hash C5D7F2A3
+ sample 18:
+ time = 1035000
+ flags = 1
+ data = length 418, hash 733A35AE
+ sample 19:
+ time = 1069829
+ flags = 1
+ data = length 418, hash DE46E5D3
+ sample 20:
+ time = 1104000
+ flags = 1
+ data = length 418, hash 56AB8D37
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/sample.mkv.2.dump b/tree/testdata/src/test/assets/mkv/sample.mkv.2.dump
new file mode 100644
index 0000000..e965e50
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/sample.mkv.2.dump
@@ -0,0 +1,108 @@
+seekMap:
+ isSeekable = true
+ duration = 1104000
+ getPosition(0) = [[timeUs=67000, position=5576]]
+ getPosition(1) = [[timeUs=67000, position=5576]]
+ getPosition(552000) = [[timeUs=547000, position=77334], [timeUs=567000, position=84155]]
+ getPosition(1104000) = [[timeUs=1035000, position=106570]]
+numberOfTracks = 2
+track 1:
+ total output bytes = 8360
+ sample count = 9
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ selectionFlags = 1
+ language = und
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 1:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 2:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 3:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 4:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 5:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 6:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 7:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 8:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+track 2:
+ total output bytes = 4180
+ sample count = 10
+ format 0:
+ id = 2
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 791000
+ flags = 1
+ data = length 418, hash 9EFBFD97
+ sample 1:
+ time = 826000
+ flags = 1
+ data = length 418, hash DF1A460C
+ sample 2:
+ time = 860829
+ flags = 1
+ data = length 418, hash 2BDB56A
+ sample 3:
+ time = 896000
+ flags = 1
+ data = length 418, hash CA230060
+ sample 4:
+ time = 931000
+ flags = 1
+ data = length 418, hash D2F19F41
+ sample 5:
+ time = 965000
+ flags = 1
+ data = length 418, hash AF392D79
+ sample 6:
+ time = 999829
+ flags = 1
+ data = length 418, hash C5D7F2A3
+ sample 7:
+ time = 1035000
+ flags = 1
+ data = length 418, hash 733A35AE
+ sample 8:
+ time = 1069829
+ flags = 1
+ data = length 418, hash DE46E5D3
+ sample 9:
+ time = 1104000
+ flags = 1
+ data = length 418, hash 56AB8D37
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/sample.mkv.3.dump b/tree/testdata/src/test/assets/mkv/sample.mkv.3.dump
new file mode 100644
index 0000000..ab6d8e0
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/sample.mkv.3.dump
@@ -0,0 +1,44 @@
+seekMap:
+ isSeekable = true
+ duration = 1104000
+ getPosition(0) = [[timeUs=67000, position=5576]]
+ getPosition(1) = [[timeUs=67000, position=5576]]
+ getPosition(552000) = [[timeUs=547000, position=77334], [timeUs=567000, position=84155]]
+ getPosition(1104000) = [[timeUs=1035000, position=106570]]
+numberOfTracks = 2
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ selectionFlags = 1
+ language = und
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+track 2:
+ total output bytes = 1254
+ sample count = 3
+ format 0:
+ id = 2
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 1035000
+ flags = 1
+ data = length 418, hash 733A35AE
+ sample 1:
+ time = 1069829
+ flags = 1
+ data = length 418, hash DE46E5D3
+ sample 2:
+ time = 1104000
+ flags = 1
+ data = length 418, hash 56AB8D37
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/sample.mkv.unknown_length.dump b/tree/testdata/src/test/assets/mkv/sample.mkv.unknown_length.dump
new file mode 100644
index 0000000..bb8c063
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/sample.mkv.unknown_length.dump
@@ -0,0 +1,268 @@
+seekMap:
+ isSeekable = true
+ duration = 1104000
+ getPosition(0) = [[timeUs=67000, position=5576]]
+ getPosition(1) = [[timeUs=67000, position=5576]]
+ getPosition(552000) = [[timeUs=547000, position=77334], [timeUs=567000, position=84155]]
+ getPosition(1104000) = [[timeUs=1035000, position=106570]]
+numberOfTracks = 2
+track 1:
+ total output bytes = 89502
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ selectionFlags = 1
+ language = und
+ initializationData:
+ data = length 30, hash F6F3D010
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 67000
+ flags = 1
+ data = length 36477, hash F0F36CFE
+ sample 1:
+ time = 134000
+ flags = 0
+ data = length 5341, hash 40B85E2
+ sample 2:
+ time = 100000
+ flags = 0
+ data = length 596, hash 357B4D92
+ sample 3:
+ time = 267000
+ flags = 0
+ data = length 7704, hash A39EDA06
+ sample 4:
+ time = 200000
+ flags = 0
+ data = length 989, hash 2813C72D
+ sample 5:
+ time = 167000
+ flags = 0
+ data = length 721, hash C50D1C73
+ sample 6:
+ time = 234000
+ flags = 0
+ data = length 519, hash 65FE1911
+ sample 7:
+ time = 400000
+ flags = 0
+ data = length 6160, hash E1CAC0EC
+ sample 8:
+ time = 334000
+ flags = 0
+ data = length 953, hash 7160C661
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 620, hash 7A7AE07C
+ sample 10:
+ time = 367000
+ flags = 0
+ data = length 405, hash 5CC7F4E7
+ sample 11:
+ time = 500000
+ flags = 0
+ data = length 4852, hash 9DB6979D
+ sample 12:
+ time = 467000
+ flags = 0
+ data = length 547, hash E31A6979
+ sample 13:
+ time = 434000
+ flags = 0
+ data = length 570, hash FEC40D00
+ sample 14:
+ time = 634000
+ flags = 0
+ data = length 5525, hash 7C478F7E
+ sample 15:
+ time = 567000
+ flags = 0
+ data = length 1082, hash DA07059A
+ sample 16:
+ time = 534000
+ flags = 0
+ data = length 807, hash 93478E6B
+ sample 17:
+ time = 600000
+ flags = 0
+ data = length 744, hash 9A8E6026
+ sample 18:
+ time = 767000
+ flags = 0
+ data = length 4732, hash C73B23C0
+ sample 19:
+ time = 700000
+ flags = 0
+ data = length 1004, hash 8A19A228
+ sample 20:
+ time = 667000
+ flags = 0
+ data = length 794, hash 8126022C
+ sample 21:
+ time = 734000
+ flags = 0
+ data = length 645, hash F08300E5
+ sample 22:
+ time = 900000
+ flags = 0
+ data = length 2684, hash 727FE378
+ sample 23:
+ time = 834000
+ flags = 0
+ data = length 787, hash 419A7821
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 649, hash 5C159346
+ sample 25:
+ time = 867000
+ flags = 0
+ data = length 509, hash F912D655
+ sample 26:
+ time = 1034000
+ flags = 0
+ data = length 1226, hash 29815C21
+ sample 27:
+ time = 967000
+ flags = 0
+ data = length 898, hash D997AD0A
+ sample 28:
+ time = 934000
+ flags = 0
+ data = length 476, hash A0423645
+ sample 29:
+ time = 1000000
+ flags = 0
+ data = length 486, hash DDF32CBB
+track 2:
+ total output bytes = 12120
+ sample count = 29
+ format 0:
+ id = 2
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ selectionFlags = 1
+ language = und
+ sample 0:
+ time = 129000
+ flags = 1
+ data = length 416, hash 211F2286
+ sample 1:
+ time = 164000
+ flags = 1
+ data = length 418, hash 77425A86
+ sample 2:
+ time = 198829
+ flags = 1
+ data = length 418, hash A0FE5CA1
+ sample 3:
+ time = 233000
+ flags = 1
+ data = length 418, hash 2309B066
+ sample 4:
+ time = 268000
+ flags = 1
+ data = length 418, hash 928A653B
+ sample 5:
+ time = 303000
+ flags = 1
+ data = length 418, hash 3422F0CB
+ sample 6:
+ time = 337829
+ flags = 1
+ data = length 418, hash EFF43D5B
+ sample 7:
+ time = 373000
+ flags = 1
+ data = length 418, hash FC8093C7
+ sample 8:
+ time = 408000
+ flags = 1
+ data = length 418, hash CCC08A16
+ sample 9:
+ time = 443000
+ flags = 1
+ data = length 418, hash 2A6EE863
+ sample 10:
+ time = 477829
+ flags = 1
+ data = length 418, hash D69A9251
+ sample 11:
+ time = 512000
+ flags = 1
+ data = length 418, hash BCFB758D
+ sample 12:
+ time = 547000
+ flags = 1
+ data = length 418, hash 11B66799
+ sample 13:
+ time = 581829
+ flags = 1
+ data = length 418, hash C824D392
+ sample 14:
+ time = 617000
+ flags = 1
+ data = length 418, hash C167D872
+ sample 15:
+ time = 652000
+ flags = 1
+ data = length 418, hash 4221C855
+ sample 16:
+ time = 687000
+ flags = 1
+ data = length 418, hash 4D4FF934
+ sample 17:
+ time = 721829
+ flags = 1
+ data = length 418, hash 984AA025
+ sample 18:
+ time = 757000
+ flags = 1
+ data = length 418, hash BB788B46
+ sample 19:
+ time = 791000
+ flags = 1
+ data = length 418, hash 9EFBFD97
+ sample 20:
+ time = 826000
+ flags = 1
+ data = length 418, hash DF1A460C
+ sample 21:
+ time = 860829
+ flags = 1
+ data = length 418, hash 2BDB56A
+ sample 22:
+ time = 896000
+ flags = 1
+ data = length 418, hash CA230060
+ sample 23:
+ time = 931000
+ flags = 1
+ data = length 418, hash D2F19F41
+ sample 24:
+ time = 965000
+ flags = 1
+ data = length 418, hash AF392D79
+ sample 25:
+ time = 999829
+ flags = 1
+ data = length 418, hash C5D7F2A3
+ sample 26:
+ time = 1035000
+ flags = 1
+ data = length 418, hash 733A35AE
+ sample 27:
+ time = 1069829
+ flags = 1
+ data = length 418, hash DE46E5D3
+ sample 28:
+ time = 1104000
+ flags = 1
+ data = length 418, hash 56AB8D37
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_altref.webm b/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm
similarity index 100%
rename from tree/library/extractor/src/test/assets/mkv/subsample_encrypted_altref.webm
rename to tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm
Binary files differ
diff --git a/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump b/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump
new file mode 100644
index 0000000..303654d
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.0.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = false
+ duration = 1000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 39
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = video/x-vnd.on2.vp9
+ width = 360
+ height = 240
+ selectionFlags = 1
+ language = en
+ drmInitData = 1305012705
+ sample 0:
+ time = 0
+ flags = 1073741824
+ data = length 39, hash B7FE77F4
+ crypto mode = 1
+ encryption key = length 16, hash 4CE944CF
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.unknown_length.dump b/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.unknown_length.dump
new file mode 100644
index 0000000..303654d
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/subsample_encrypted_altref.webm.unknown_length.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = false
+ duration = 1000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 39
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = video/x-vnd.on2.vp9
+ width = 360
+ height = 240
+ selectionFlags = 1
+ language = en
+ drmInitData = 1305012705
+ sample 0:
+ time = 0
+ flags = 1073741824
+ data = length 39, hash B7FE77F4
+ crypto mode = 1
+ encryption key = length 16, hash 4CE944CF
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mkv/subsample_encrypted_noaltref.webm b/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm
similarity index 100%
rename from tree/library/extractor/src/test/assets/mkv/subsample_encrypted_noaltref.webm
rename to tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm
Binary files differ
diff --git a/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump b/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump
new file mode 100644
index 0000000..af5a5af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.0.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = false
+ duration = 1000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 24
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = video/x-vnd.on2.vp9
+ width = 360
+ height = 240
+ selectionFlags = 1
+ language = en
+ drmInitData = 1305012705
+ sample 0:
+ time = 0
+ flags = 1073741824
+ data = length 24, hash E58668B1
+ crypto mode = 1
+ encryption key = length 16, hash 4CE944CF
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.unknown_length.dump b/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.unknown_length.dump
new file mode 100644
index 0000000..af5a5af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mkv/subsample_encrypted_noaltref.webm.unknown_length.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = false
+ duration = 1000
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1:
+ total output bytes = 24
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = video/x-vnd.on2.vp9
+ width = 360
+ height = 240
+ selectionFlags = 1
+ language = en
+ drmInitData = 1305012705
+ sample 0:
+ time = 0
+ flags = 1073741824
+ data = length 24, hash E58668B1
+ crypto mode = 1
+ encryption key = length 16, hash 4CE944CF
+tracksEnded = true
diff --git a/tree/library/core/src/androidTest/assets/binary/1024_incrementing_bytes.mp3 b/tree/testdata/src/test/assets/mp3/1024_incrementing_bytes.mp3
similarity index 100%
rename from tree/library/core/src/androidTest/assets/binary/1024_incrementing_bytes.mp3
rename to tree/testdata/src/test/assets/mp3/1024_incrementing_bytes.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-constant-frame-size-no-seek-table.mp3 b/tree/testdata/src/test/assets/mp3/bear-cbr-constant-frame-size-no-seek-table.mp3
new file mode 100644
index 0000000..ce318ca
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-constant-frame-size-no-seek-table.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3 b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3
new file mode 100644
index 0000000..a329a49
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.0.dump b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.0.dump
new file mode 100644
index 0000000..ba8b3d5
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.0.dump
@@ -0,0 +1,449 @@
+seekMap:
+ isSeekable = true
+ duration = 2821187
+ getPosition(0) = [[timeUs=0, position=240]]
+ getPosition(1) = [[timeUs=0, position=240], [timeUs=26062, position=657]]
+ getPosition(1410593) = [[timeUs=1407375, position=22758], [timeUs=1433437, position=23175]]
+ getPosition(2821187) = [[timeUs=2795125, position=44962]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 45139
+ sample count = 108
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 417, hash C4565176
+ sample 1:
+ time = 26122
+ flags = 1
+ data = length 418, hash 70AEC448
+ sample 2:
+ time = 52244
+ flags = 1
+ data = length 418, hash 835A8FB9
+ sample 3:
+ time = 78367
+ flags = 1
+ data = length 418, hash 3A9672BF
+ sample 4:
+ time = 104489
+ flags = 1
+ data = length 418, hash 8DBE60F9
+ sample 5:
+ time = 130612
+ flags = 1
+ data = length 418, hash 23D0867B
+ sample 6:
+ time = 156734
+ flags = 1
+ data = length 418, hash 7780AAB9
+ sample 7:
+ time = 182857
+ flags = 1
+ data = length 418, hash 3F63B2D1
+ sample 8:
+ time = 208979
+ flags = 1
+ data = length 418, hash 7A33CEBD
+ sample 9:
+ time = 235102
+ flags = 1
+ data = length 418, hash DF31D514
+ sample 10:
+ time = 261224
+ flags = 1
+ data = length 418, hash 26FA2C86
+ sample 11:
+ time = 287346
+ flags = 1
+ data = length 418, hash D9C7FB1
+ sample 12:
+ time = 313469
+ flags = 1
+ data = length 418, hash B1C40DC8
+ sample 13:
+ time = 339591
+ flags = 1
+ data = length 418, hash 1C953BEE
+ sample 14:
+ time = 365714
+ flags = 1
+ data = length 418, hash A6053C6
+ sample 15:
+ time = 391836
+ flags = 1
+ data = length 418, hash 2D90325A
+ sample 16:
+ time = 417959
+ flags = 1
+ data = length 418, hash 11A84918
+ sample 17:
+ time = 444081
+ flags = 1
+ data = length 418, hash 30F1A19A
+ sample 18:
+ time = 470204
+ flags = 1
+ data = length 418, hash 70EC67FF
+ sample 19:
+ time = 496326
+ flags = 1
+ data = length 418, hash 7BAF5828
+ sample 20:
+ time = 522448
+ flags = 1
+ data = length 418, hash 8E43B85E
+ sample 21:
+ time = 548571
+ flags = 1
+ data = length 418, hash E9A5EE78
+ sample 22:
+ time = 574693
+ flags = 1
+ data = length 418, hash F79931F8
+ sample 23:
+ time = 600816
+ flags = 1
+ data = length 418, hash C0308B40
+ sample 24:
+ time = 626938
+ flags = 1
+ data = length 418, hash 3D2E55B
+ sample 25:
+ time = 653061
+ flags = 1
+ data = length 417, hash D74A61AF
+ sample 26:
+ time = 679183
+ flags = 1
+ data = length 418, hash 96F104B1
+ sample 27:
+ time = 705306
+ flags = 1
+ data = length 418, hash CE12216
+ sample 28:
+ time = 731428
+ flags = 1
+ data = length 418, hash 899EA46D
+ sample 29:
+ time = 757551
+ flags = 1
+ data = length 418, hash 1208BBC5
+ sample 30:
+ time = 783673
+ flags = 1
+ data = length 418, hash 49F22D4D
+ sample 31:
+ time = 809795
+ flags = 1
+ data = length 418, hash 56D959B0
+ sample 32:
+ time = 835918
+ flags = 1
+ data = length 418, hash 5EC6FF8C
+ sample 33:
+ time = 862040
+ flags = 1
+ data = length 418, hash 380B6E00
+ sample 34:
+ time = 888163
+ flags = 1
+ data = length 418, hash 19494E6B
+ sample 35:
+ time = 914285
+ flags = 1
+ data = length 418, hash C751B033
+ sample 36:
+ time = 940408
+ flags = 1
+ data = length 418, hash 5F7C6DBA
+ sample 37:
+ time = 966530
+ flags = 1
+ data = length 418, hash D77E6530
+ sample 38:
+ time = 992653
+ flags = 1
+ data = length 418, hash 48A694AB
+ sample 39:
+ time = 1018775
+ flags = 1
+ data = length 418, hash A979850E
+ sample 40:
+ time = 1044897
+ flags = 1
+ data = length 418, hash 7688E4B1
+ sample 41:
+ time = 1071020
+ flags = 1
+ data = length 418, hash 255AF933
+ sample 42:
+ time = 1097142
+ flags = 1
+ data = length 418, hash D58AC838
+ sample 43:
+ time = 1123265
+ flags = 1
+ data = length 418, hash A38DC7B
+ sample 44:
+ time = 1149387
+ flags = 1
+ data = length 418, hash EA0CA21
+ sample 45:
+ time = 1175510
+ flags = 1
+ data = length 418, hash DF99B54B
+ sample 46:
+ time = 1201632
+ flags = 1
+ data = length 418, hash A1532134
+ sample 47:
+ time = 1227755
+ flags = 1
+ data = length 418, hash 520EC187
+ sample 48:
+ time = 1253877
+ flags = 1
+ data = length 418, hash 5E38E4F
+ sample 49:
+ time = 1280000
+ flags = 1
+ data = length 417, hash 4D3526FB
+ sample 50:
+ time = 1306122
+ flags = 1
+ data = length 418, hash D99092CA
+ sample 51:
+ time = 1332244
+ flags = 1
+ data = length 418, hash EDB10D8E
+ sample 52:
+ time = 1358367
+ flags = 1
+ data = length 418, hash 5B5F6439
+ sample 53:
+ time = 1384489
+ flags = 1
+ data = length 418, hash 947E2739
+ sample 54:
+ time = 1410612
+ flags = 1
+ data = length 418, hash 8C1FF29C
+ sample 55:
+ time = 1436734
+ flags = 1
+ data = length 418, hash FEADC9C3
+ sample 56:
+ time = 1462857
+ flags = 1
+ data = length 418, hash BB82E0C8
+ sample 57:
+ time = 1488979
+ flags = 1
+ data = length 418, hash 8D1494AF
+ sample 58:
+ time = 1515102
+ flags = 1
+ data = length 418, hash E8C4265C
+ sample 59:
+ time = 1541224
+ flags = 1
+ data = length 418, hash BC8F59AE
+ sample 60:
+ time = 1567346
+ flags = 1
+ data = length 418, hash C8C5DCBD
+ sample 61:
+ time = 1593469
+ flags = 1
+ data = length 418, hash 43C3D85B
+ sample 62:
+ time = 1619591
+ flags = 1
+ data = length 418, hash 238C1AFE
+ sample 63:
+ time = 1645714
+ flags = 1
+ data = length 418, hash F6099191
+ sample 64:
+ time = 1671836
+ flags = 1
+ data = length 418, hash D236BB0E
+ sample 65:
+ time = 1697959
+ flags = 1
+ data = length 418, hash 58B5B714
+ sample 66:
+ time = 1724081
+ flags = 1
+ data = length 418, hash A9DDDD52
+ sample 67:
+ time = 1750204
+ flags = 1
+ data = length 418, hash 85E7D11E
+ sample 68:
+ time = 1776326
+ flags = 1
+ data = length 418, hash 9E9D8FF4
+ sample 69:
+ time = 1802448
+ flags = 1
+ data = length 418, hash 6FF9060D
+ sample 70:
+ time = 1828571
+ flags = 1
+ data = length 418, hash 4F1FC4F5
+ sample 71:
+ time = 1854693
+ flags = 1
+ data = length 418, hash EF9885AA
+ sample 72:
+ time = 1880816
+ flags = 1
+ data = length 418, hash 7872C242
+ sample 73:
+ time = 1906938
+ flags = 1
+ data = length 418, hash EB6FEAED
+ sample 74:
+ time = 1933061
+ flags = 1
+ data = length 417, hash B02D8CF0
+ sample 75:
+ time = 1959183
+ flags = 1
+ data = length 418, hash EFB6C2DD
+ sample 76:
+ time = 1985306
+ flags = 1
+ data = length 418, hash B733E449
+ sample 77:
+ time = 2011428
+ flags = 1
+ data = length 418, hash 617B155E
+ sample 78:
+ time = 2037551
+ flags = 1
+ data = length 418, hash AE626B2E
+ sample 79:
+ time = 2063673
+ flags = 1
+ data = length 418, hash F5E232C
+ sample 80:
+ time = 2089795
+ flags = 1
+ data = length 418, hash B5F4DC29
+ sample 81:
+ time = 2115918
+ flags = 1
+ data = length 418, hash C791E3B5
+ sample 82:
+ time = 2142040
+ flags = 1
+ data = length 418, hash F42A6BDB
+ sample 83:
+ time = 2168163
+ flags = 1
+ data = length 418, hash FDAEEFE6
+ sample 84:
+ time = 2194285
+ flags = 1
+ data = length 418, hash 62AC2513
+ sample 85:
+ time = 2220408
+ flags = 1
+ data = length 418, hash A4B46783
+ sample 86:
+ time = 2246530
+ flags = 1
+ data = length 418, hash 9B7DFEFE
+ sample 87:
+ time = 2272653
+ flags = 1
+ data = length 418, hash 4010F89A
+ sample 88:
+ time = 2298775
+ flags = 1
+ data = length 418, hash 33467FC1
+ sample 89:
+ time = 2324897
+ flags = 1
+ data = length 418, hash 1DFAE1E9
+ sample 90:
+ time = 2351020
+ flags = 1
+ data = length 418, hash C208D375
+ sample 91:
+ time = 2377142
+ flags = 1
+ data = length 418, hash CD430C30
+ sample 92:
+ time = 2403265
+ flags = 1
+ data = length 418, hash 5A6F8065
+ sample 93:
+ time = 2429387
+ flags = 1
+ data = length 418, hash 7177BD8B
+ sample 94:
+ time = 2455510
+ flags = 1
+ data = length 418, hash 51C1F29B
+ sample 95:
+ time = 2481632
+ flags = 1
+ data = length 418, hash 868A0084
+ sample 96:
+ time = 2507755
+ flags = 1
+ data = length 418, hash 1E9C03E1
+ sample 97:
+ time = 2533877
+ flags = 1
+ data = length 418, hash 10069B68
+ sample 98:
+ time = 2560000
+ flags = 1
+ data = length 417, hash CC5B751D
+ sample 99:
+ time = 2586122
+ flags = 1
+ data = length 418, hash 837D650
+ sample 100:
+ time = 2612244
+ flags = 1
+ data = length 418, hash 43B75632
+ sample 101:
+ time = 2638367
+ flags = 1
+ data = length 418, hash 86E0652
+ sample 102:
+ time = 2664489
+ flags = 1
+ data = length 418, hash 4DEC63E7
+ sample 103:
+ time = 2690612
+ flags = 1
+ data = length 418, hash F094F330
+ sample 104:
+ time = 2716734
+ flags = 1
+ data = length 418, hash 2C9CAA4
+ sample 105:
+ time = 2742857
+ flags = 1
+ data = length 418, hash 1E903FFE
+ sample 106:
+ time = 2768979
+ flags = 1
+ data = length 418, hash F276CF72
+ sample 107:
+ time = 2795102
+ flags = 1
+ data = length 418, hash 1C081463
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.1.dump b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.1.dump
new file mode 100644
index 0000000..723354f
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.1.dump
@@ -0,0 +1,305 @@
+seekMap:
+ isSeekable = true
+ duration = 2821187
+ getPosition(0) = [[timeUs=0, position=240]]
+ getPosition(1) = [[timeUs=0, position=240], [timeUs=26062, position=657]]
+ getPosition(1410593) = [[timeUs=1407375, position=22758], [timeUs=1433437, position=23175]]
+ getPosition(2821187) = [[timeUs=2795125, position=44962]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 30093
+ sample count = 72
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 940375
+ flags = 1
+ data = length 418, hash 5F7C6DBA
+ sample 1:
+ time = 966497
+ flags = 1
+ data = length 418, hash D77E6530
+ sample 2:
+ time = 992619
+ flags = 1
+ data = length 418, hash 48A694AB
+ sample 3:
+ time = 1018742
+ flags = 1
+ data = length 418, hash A979850E
+ sample 4:
+ time = 1044864
+ flags = 1
+ data = length 418, hash 7688E4B1
+ sample 5:
+ time = 1070987
+ flags = 1
+ data = length 418, hash 255AF933
+ sample 6:
+ time = 1097109
+ flags = 1
+ data = length 418, hash D58AC838
+ sample 7:
+ time = 1123232
+ flags = 1
+ data = length 418, hash A38DC7B
+ sample 8:
+ time = 1149354
+ flags = 1
+ data = length 418, hash EA0CA21
+ sample 9:
+ time = 1175477
+ flags = 1
+ data = length 418, hash DF99B54B
+ sample 10:
+ time = 1201599
+ flags = 1
+ data = length 418, hash A1532134
+ sample 11:
+ time = 1227721
+ flags = 1
+ data = length 418, hash 520EC187
+ sample 12:
+ time = 1253844
+ flags = 1
+ data = length 418, hash 5E38E4F
+ sample 13:
+ time = 1279966
+ flags = 1
+ data = length 417, hash 4D3526FB
+ sample 14:
+ time = 1306089
+ flags = 1
+ data = length 418, hash D99092CA
+ sample 15:
+ time = 1332211
+ flags = 1
+ data = length 418, hash EDB10D8E
+ sample 16:
+ time = 1358334
+ flags = 1
+ data = length 418, hash 5B5F6439
+ sample 17:
+ time = 1384456
+ flags = 1
+ data = length 418, hash 947E2739
+ sample 18:
+ time = 1410579
+ flags = 1
+ data = length 418, hash 8C1FF29C
+ sample 19:
+ time = 1436701
+ flags = 1
+ data = length 418, hash FEADC9C3
+ sample 20:
+ time = 1462823
+ flags = 1
+ data = length 418, hash BB82E0C8
+ sample 21:
+ time = 1488946
+ flags = 1
+ data = length 418, hash 8D1494AF
+ sample 22:
+ time = 1515068
+ flags = 1
+ data = length 418, hash E8C4265C
+ sample 23:
+ time = 1541191
+ flags = 1
+ data = length 418, hash BC8F59AE
+ sample 24:
+ time = 1567313
+ flags = 1
+ data = length 418, hash C8C5DCBD
+ sample 25:
+ time = 1593436
+ flags = 1
+ data = length 418, hash 43C3D85B
+ sample 26:
+ time = 1619558
+ flags = 1
+ data = length 418, hash 238C1AFE
+ sample 27:
+ time = 1645681
+ flags = 1
+ data = length 418, hash F6099191
+ sample 28:
+ time = 1671803
+ flags = 1
+ data = length 418, hash D236BB0E
+ sample 29:
+ time = 1697926
+ flags = 1
+ data = length 418, hash 58B5B714
+ sample 30:
+ time = 1724048
+ flags = 1
+ data = length 418, hash A9DDDD52
+ sample 31:
+ time = 1750170
+ flags = 1
+ data = length 418, hash 85E7D11E
+ sample 32:
+ time = 1776293
+ flags = 1
+ data = length 418, hash 9E9D8FF4
+ sample 33:
+ time = 1802415
+ flags = 1
+ data = length 418, hash 6FF9060D
+ sample 34:
+ time = 1828538
+ flags = 1
+ data = length 418, hash 4F1FC4F5
+ sample 35:
+ time = 1854660
+ flags = 1
+ data = length 418, hash EF9885AA
+ sample 36:
+ time = 1880783
+ flags = 1
+ data = length 418, hash 7872C242
+ sample 37:
+ time = 1906905
+ flags = 1
+ data = length 418, hash EB6FEAED
+ sample 38:
+ time = 1933028
+ flags = 1
+ data = length 417, hash B02D8CF0
+ sample 39:
+ time = 1959150
+ flags = 1
+ data = length 418, hash EFB6C2DD
+ sample 40:
+ time = 1985272
+ flags = 1
+ data = length 418, hash B733E449
+ sample 41:
+ time = 2011395
+ flags = 1
+ data = length 418, hash 617B155E
+ sample 42:
+ time = 2037517
+ flags = 1
+ data = length 418, hash AE626B2E
+ sample 43:
+ time = 2063640
+ flags = 1
+ data = length 418, hash F5E232C
+ sample 44:
+ time = 2089762
+ flags = 1
+ data = length 418, hash B5F4DC29
+ sample 45:
+ time = 2115885
+ flags = 1
+ data = length 418, hash C791E3B5
+ sample 46:
+ time = 2142007
+ flags = 1
+ data = length 418, hash F42A6BDB
+ sample 47:
+ time = 2168130
+ flags = 1
+ data = length 418, hash FDAEEFE6
+ sample 48:
+ time = 2194252
+ flags = 1
+ data = length 418, hash 62AC2513
+ sample 49:
+ time = 2220375
+ flags = 1
+ data = length 418, hash A4B46783
+ sample 50:
+ time = 2246497
+ flags = 1
+ data = length 418, hash 9B7DFEFE
+ sample 51:
+ time = 2272619
+ flags = 1
+ data = length 418, hash 4010F89A
+ sample 52:
+ time = 2298742
+ flags = 1
+ data = length 418, hash 33467FC1
+ sample 53:
+ time = 2324864
+ flags = 1
+ data = length 418, hash 1DFAE1E9
+ sample 54:
+ time = 2350987
+ flags = 1
+ data = length 418, hash C208D375
+ sample 55:
+ time = 2377109
+ flags = 1
+ data = length 418, hash CD430C30
+ sample 56:
+ time = 2403232
+ flags = 1
+ data = length 418, hash 5A6F8065
+ sample 57:
+ time = 2429354
+ flags = 1
+ data = length 418, hash 7177BD8B
+ sample 58:
+ time = 2455477
+ flags = 1
+ data = length 418, hash 51C1F29B
+ sample 59:
+ time = 2481599
+ flags = 1
+ data = length 418, hash 868A0084
+ sample 60:
+ time = 2507721
+ flags = 1
+ data = length 418, hash 1E9C03E1
+ sample 61:
+ time = 2533844
+ flags = 1
+ data = length 418, hash 10069B68
+ sample 62:
+ time = 2559966
+ flags = 1
+ data = length 417, hash CC5B751D
+ sample 63:
+ time = 2586089
+ flags = 1
+ data = length 418, hash 837D650
+ sample 64:
+ time = 2612211
+ flags = 1
+ data = length 418, hash 43B75632
+ sample 65:
+ time = 2638334
+ flags = 1
+ data = length 418, hash 86E0652
+ sample 66:
+ time = 2664456
+ flags = 1
+ data = length 418, hash 4DEC63E7
+ sample 67:
+ time = 2690579
+ flags = 1
+ data = length 418, hash F094F330
+ sample 68:
+ time = 2716701
+ flags = 1
+ data = length 418, hash 2C9CAA4
+ sample 69:
+ time = 2742823
+ flags = 1
+ data = length 418, hash 1E903FFE
+ sample 70:
+ time = 2768946
+ flags = 1
+ data = length 418, hash F276CF72
+ sample 71:
+ time = 2795068
+ flags = 1
+ data = length 418, hash 1C081463
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.2.dump b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.2.dump
new file mode 100644
index 0000000..00bf3f6
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.2.dump
@@ -0,0 +1,161 @@
+seekMap:
+ isSeekable = true
+ duration = 2821187
+ getPosition(0) = [[timeUs=0, position=240]]
+ getPosition(1) = [[timeUs=0, position=240], [timeUs=26062, position=657]]
+ getPosition(1410593) = [[timeUs=1407375, position=22758], [timeUs=1433437, position=23175]]
+ getPosition(2821187) = [[timeUs=2795125, position=44962]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 15046
+ sample count = 36
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 1880812
+ flags = 1
+ data = length 418, hash 7872C242
+ sample 1:
+ time = 1906934
+ flags = 1
+ data = length 418, hash EB6FEAED
+ sample 2:
+ time = 1933056
+ flags = 1
+ data = length 417, hash B02D8CF0
+ sample 3:
+ time = 1959179
+ flags = 1
+ data = length 418, hash EFB6C2DD
+ sample 4:
+ time = 1985301
+ flags = 1
+ data = length 418, hash B733E449
+ sample 5:
+ time = 2011424
+ flags = 1
+ data = length 418, hash 617B155E
+ sample 6:
+ time = 2037546
+ flags = 1
+ data = length 418, hash AE626B2E
+ sample 7:
+ time = 2063669
+ flags = 1
+ data = length 418, hash F5E232C
+ sample 8:
+ time = 2089791
+ flags = 1
+ data = length 418, hash B5F4DC29
+ sample 9:
+ time = 2115914
+ flags = 1
+ data = length 418, hash C791E3B5
+ sample 10:
+ time = 2142036
+ flags = 1
+ data = length 418, hash F42A6BDB
+ sample 11:
+ time = 2168158
+ flags = 1
+ data = length 418, hash FDAEEFE6
+ sample 12:
+ time = 2194281
+ flags = 1
+ data = length 418, hash 62AC2513
+ sample 13:
+ time = 2220403
+ flags = 1
+ data = length 418, hash A4B46783
+ sample 14:
+ time = 2246526
+ flags = 1
+ data = length 418, hash 9B7DFEFE
+ sample 15:
+ time = 2272648
+ flags = 1
+ data = length 418, hash 4010F89A
+ sample 16:
+ time = 2298771
+ flags = 1
+ data = length 418, hash 33467FC1
+ sample 17:
+ time = 2324893
+ flags = 1
+ data = length 418, hash 1DFAE1E9
+ sample 18:
+ time = 2351016
+ flags = 1
+ data = length 418, hash C208D375
+ sample 19:
+ time = 2377138
+ flags = 1
+ data = length 418, hash CD430C30
+ sample 20:
+ time = 2403260
+ flags = 1
+ data = length 418, hash 5A6F8065
+ sample 21:
+ time = 2429383
+ flags = 1
+ data = length 418, hash 7177BD8B
+ sample 22:
+ time = 2455505
+ flags = 1
+ data = length 418, hash 51C1F29B
+ sample 23:
+ time = 2481628
+ flags = 1
+ data = length 418, hash 868A0084
+ sample 24:
+ time = 2507750
+ flags = 1
+ data = length 418, hash 1E9C03E1
+ sample 25:
+ time = 2533873
+ flags = 1
+ data = length 418, hash 10069B68
+ sample 26:
+ time = 2559995
+ flags = 1
+ data = length 417, hash CC5B751D
+ sample 27:
+ time = 2586118
+ flags = 1
+ data = length 418, hash 837D650
+ sample 28:
+ time = 2612240
+ flags = 1
+ data = length 418, hash 43B75632
+ sample 29:
+ time = 2638363
+ flags = 1
+ data = length 418, hash 86E0652
+ sample 30:
+ time = 2664485
+ flags = 1
+ data = length 418, hash 4DEC63E7
+ sample 31:
+ time = 2690607
+ flags = 1
+ data = length 418, hash F094F330
+ sample 32:
+ time = 2716730
+ flags = 1
+ data = length 418, hash 2C9CAA4
+ sample 33:
+ time = 2742852
+ flags = 1
+ data = length 418, hash 1E903FFE
+ sample 34:
+ time = 2768975
+ flags = 1
+ data = length 418, hash F276CF72
+ sample 35:
+ time = 2795097
+ flags = 1
+ data = length 418, hash 1C081463
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.3.dump b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.3.dump
new file mode 100644
index 0000000..231aede
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.3.dump
@@ -0,0 +1,17 @@
+seekMap:
+ isSeekable = true
+ duration = 2821187
+ getPosition(0) = [[timeUs=0, position=240]]
+ getPosition(1) = [[timeUs=0, position=240], [timeUs=26062, position=657]]
+ getPosition(1410593) = [[timeUs=1407375, position=22758], [timeUs=1433437, position=23175]]
+ getPosition(2821187) = [[timeUs=2795125, position=44962]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.unknown_length.dump b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.unknown_length.dump
new file mode 100644
index 0000000..bedbf6f
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-cbr-variable-frame-size-no-seek-table.mp3.unknown_length.dump
@@ -0,0 +1,446 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=240]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 45139
+ sample count = 108
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 417, hash C4565176
+ sample 1:
+ time = 26122
+ flags = 1
+ data = length 418, hash 70AEC448
+ sample 2:
+ time = 52244
+ flags = 1
+ data = length 418, hash 835A8FB9
+ sample 3:
+ time = 78367
+ flags = 1
+ data = length 418, hash 3A9672BF
+ sample 4:
+ time = 104489
+ flags = 1
+ data = length 418, hash 8DBE60F9
+ sample 5:
+ time = 130612
+ flags = 1
+ data = length 418, hash 23D0867B
+ sample 6:
+ time = 156734
+ flags = 1
+ data = length 418, hash 7780AAB9
+ sample 7:
+ time = 182857
+ flags = 1
+ data = length 418, hash 3F63B2D1
+ sample 8:
+ time = 208979
+ flags = 1
+ data = length 418, hash 7A33CEBD
+ sample 9:
+ time = 235102
+ flags = 1
+ data = length 418, hash DF31D514
+ sample 10:
+ time = 261224
+ flags = 1
+ data = length 418, hash 26FA2C86
+ sample 11:
+ time = 287346
+ flags = 1
+ data = length 418, hash D9C7FB1
+ sample 12:
+ time = 313469
+ flags = 1
+ data = length 418, hash B1C40DC8
+ sample 13:
+ time = 339591
+ flags = 1
+ data = length 418, hash 1C953BEE
+ sample 14:
+ time = 365714
+ flags = 1
+ data = length 418, hash A6053C6
+ sample 15:
+ time = 391836
+ flags = 1
+ data = length 418, hash 2D90325A
+ sample 16:
+ time = 417959
+ flags = 1
+ data = length 418, hash 11A84918
+ sample 17:
+ time = 444081
+ flags = 1
+ data = length 418, hash 30F1A19A
+ sample 18:
+ time = 470204
+ flags = 1
+ data = length 418, hash 70EC67FF
+ sample 19:
+ time = 496326
+ flags = 1
+ data = length 418, hash 7BAF5828
+ sample 20:
+ time = 522448
+ flags = 1
+ data = length 418, hash 8E43B85E
+ sample 21:
+ time = 548571
+ flags = 1
+ data = length 418, hash E9A5EE78
+ sample 22:
+ time = 574693
+ flags = 1
+ data = length 418, hash F79931F8
+ sample 23:
+ time = 600816
+ flags = 1
+ data = length 418, hash C0308B40
+ sample 24:
+ time = 626938
+ flags = 1
+ data = length 418, hash 3D2E55B
+ sample 25:
+ time = 653061
+ flags = 1
+ data = length 417, hash D74A61AF
+ sample 26:
+ time = 679183
+ flags = 1
+ data = length 418, hash 96F104B1
+ sample 27:
+ time = 705306
+ flags = 1
+ data = length 418, hash CE12216
+ sample 28:
+ time = 731428
+ flags = 1
+ data = length 418, hash 899EA46D
+ sample 29:
+ time = 757551
+ flags = 1
+ data = length 418, hash 1208BBC5
+ sample 30:
+ time = 783673
+ flags = 1
+ data = length 418, hash 49F22D4D
+ sample 31:
+ time = 809795
+ flags = 1
+ data = length 418, hash 56D959B0
+ sample 32:
+ time = 835918
+ flags = 1
+ data = length 418, hash 5EC6FF8C
+ sample 33:
+ time = 862040
+ flags = 1
+ data = length 418, hash 380B6E00
+ sample 34:
+ time = 888163
+ flags = 1
+ data = length 418, hash 19494E6B
+ sample 35:
+ time = 914285
+ flags = 1
+ data = length 418, hash C751B033
+ sample 36:
+ time = 940408
+ flags = 1
+ data = length 418, hash 5F7C6DBA
+ sample 37:
+ time = 966530
+ flags = 1
+ data = length 418, hash D77E6530
+ sample 38:
+ time = 992653
+ flags = 1
+ data = length 418, hash 48A694AB
+ sample 39:
+ time = 1018775
+ flags = 1
+ data = length 418, hash A979850E
+ sample 40:
+ time = 1044897
+ flags = 1
+ data = length 418, hash 7688E4B1
+ sample 41:
+ time = 1071020
+ flags = 1
+ data = length 418, hash 255AF933
+ sample 42:
+ time = 1097142
+ flags = 1
+ data = length 418, hash D58AC838
+ sample 43:
+ time = 1123265
+ flags = 1
+ data = length 418, hash A38DC7B
+ sample 44:
+ time = 1149387
+ flags = 1
+ data = length 418, hash EA0CA21
+ sample 45:
+ time = 1175510
+ flags = 1
+ data = length 418, hash DF99B54B
+ sample 46:
+ time = 1201632
+ flags = 1
+ data = length 418, hash A1532134
+ sample 47:
+ time = 1227755
+ flags = 1
+ data = length 418, hash 520EC187
+ sample 48:
+ time = 1253877
+ flags = 1
+ data = length 418, hash 5E38E4F
+ sample 49:
+ time = 1280000
+ flags = 1
+ data = length 417, hash 4D3526FB
+ sample 50:
+ time = 1306122
+ flags = 1
+ data = length 418, hash D99092CA
+ sample 51:
+ time = 1332244
+ flags = 1
+ data = length 418, hash EDB10D8E
+ sample 52:
+ time = 1358367
+ flags = 1
+ data = length 418, hash 5B5F6439
+ sample 53:
+ time = 1384489
+ flags = 1
+ data = length 418, hash 947E2739
+ sample 54:
+ time = 1410612
+ flags = 1
+ data = length 418, hash 8C1FF29C
+ sample 55:
+ time = 1436734
+ flags = 1
+ data = length 418, hash FEADC9C3
+ sample 56:
+ time = 1462857
+ flags = 1
+ data = length 418, hash BB82E0C8
+ sample 57:
+ time = 1488979
+ flags = 1
+ data = length 418, hash 8D1494AF
+ sample 58:
+ time = 1515102
+ flags = 1
+ data = length 418, hash E8C4265C
+ sample 59:
+ time = 1541224
+ flags = 1
+ data = length 418, hash BC8F59AE
+ sample 60:
+ time = 1567346
+ flags = 1
+ data = length 418, hash C8C5DCBD
+ sample 61:
+ time = 1593469
+ flags = 1
+ data = length 418, hash 43C3D85B
+ sample 62:
+ time = 1619591
+ flags = 1
+ data = length 418, hash 238C1AFE
+ sample 63:
+ time = 1645714
+ flags = 1
+ data = length 418, hash F6099191
+ sample 64:
+ time = 1671836
+ flags = 1
+ data = length 418, hash D236BB0E
+ sample 65:
+ time = 1697959
+ flags = 1
+ data = length 418, hash 58B5B714
+ sample 66:
+ time = 1724081
+ flags = 1
+ data = length 418, hash A9DDDD52
+ sample 67:
+ time = 1750204
+ flags = 1
+ data = length 418, hash 85E7D11E
+ sample 68:
+ time = 1776326
+ flags = 1
+ data = length 418, hash 9E9D8FF4
+ sample 69:
+ time = 1802448
+ flags = 1
+ data = length 418, hash 6FF9060D
+ sample 70:
+ time = 1828571
+ flags = 1
+ data = length 418, hash 4F1FC4F5
+ sample 71:
+ time = 1854693
+ flags = 1
+ data = length 418, hash EF9885AA
+ sample 72:
+ time = 1880816
+ flags = 1
+ data = length 418, hash 7872C242
+ sample 73:
+ time = 1906938
+ flags = 1
+ data = length 418, hash EB6FEAED
+ sample 74:
+ time = 1933061
+ flags = 1
+ data = length 417, hash B02D8CF0
+ sample 75:
+ time = 1959183
+ flags = 1
+ data = length 418, hash EFB6C2DD
+ sample 76:
+ time = 1985306
+ flags = 1
+ data = length 418, hash B733E449
+ sample 77:
+ time = 2011428
+ flags = 1
+ data = length 418, hash 617B155E
+ sample 78:
+ time = 2037551
+ flags = 1
+ data = length 418, hash AE626B2E
+ sample 79:
+ time = 2063673
+ flags = 1
+ data = length 418, hash F5E232C
+ sample 80:
+ time = 2089795
+ flags = 1
+ data = length 418, hash B5F4DC29
+ sample 81:
+ time = 2115918
+ flags = 1
+ data = length 418, hash C791E3B5
+ sample 82:
+ time = 2142040
+ flags = 1
+ data = length 418, hash F42A6BDB
+ sample 83:
+ time = 2168163
+ flags = 1
+ data = length 418, hash FDAEEFE6
+ sample 84:
+ time = 2194285
+ flags = 1
+ data = length 418, hash 62AC2513
+ sample 85:
+ time = 2220408
+ flags = 1
+ data = length 418, hash A4B46783
+ sample 86:
+ time = 2246530
+ flags = 1
+ data = length 418, hash 9B7DFEFE
+ sample 87:
+ time = 2272653
+ flags = 1
+ data = length 418, hash 4010F89A
+ sample 88:
+ time = 2298775
+ flags = 1
+ data = length 418, hash 33467FC1
+ sample 89:
+ time = 2324897
+ flags = 1
+ data = length 418, hash 1DFAE1E9
+ sample 90:
+ time = 2351020
+ flags = 1
+ data = length 418, hash C208D375
+ sample 91:
+ time = 2377142
+ flags = 1
+ data = length 418, hash CD430C30
+ sample 92:
+ time = 2403265
+ flags = 1
+ data = length 418, hash 5A6F8065
+ sample 93:
+ time = 2429387
+ flags = 1
+ data = length 418, hash 7177BD8B
+ sample 94:
+ time = 2455510
+ flags = 1
+ data = length 418, hash 51C1F29B
+ sample 95:
+ time = 2481632
+ flags = 1
+ data = length 418, hash 868A0084
+ sample 96:
+ time = 2507755
+ flags = 1
+ data = length 418, hash 1E9C03E1
+ sample 97:
+ time = 2533877
+ flags = 1
+ data = length 418, hash 10069B68
+ sample 98:
+ time = 2560000
+ flags = 1
+ data = length 417, hash CC5B751D
+ sample 99:
+ time = 2586122
+ flags = 1
+ data = length 418, hash 837D650
+ sample 100:
+ time = 2612244
+ flags = 1
+ data = length 418, hash 43B75632
+ sample 101:
+ time = 2638367
+ flags = 1
+ data = length 418, hash 86E0652
+ sample 102:
+ time = 2664489
+ flags = 1
+ data = length 418, hash 4DEC63E7
+ sample 103:
+ time = 2690612
+ flags = 1
+ data = length 418, hash F094F330
+ sample 104:
+ time = 2716734
+ flags = 1
+ data = length 418, hash 2C9CAA4
+ sample 105:
+ time = 2742857
+ flags = 1
+ data = length 418, hash 1E903FFE
+ sample 106:
+ time = 2768979
+ flags = 1
+ data = length 418, hash F276CF72
+ sample 107:
+ time = 2795102
+ flags = 1
+ data = length 418, hash 1C081463
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3 b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3
new file mode 100644
index 0000000..010fc37
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.0.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.0.dump
new file mode 100644
index 0000000..90edd28
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.0.dump
@@ -0,0 +1,485 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=224]]
+ getPosition(1) = [[timeUs=0, position=224], [timeUs=120000, position=2048]]
+ getPosition(1404000) = [[timeUs=1320000, position=18896], [timeUs=1440000, position=20528]]
+ getPosition(2808000) = [[timeUs=2760000, position=38168]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.1.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.1.dump
new file mode 100644
index 0000000..8a06d2b
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.1.dump
@@ -0,0 +1,345 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=224]]
+ getPosition(1) = [[timeUs=0, position=224], [timeUs=120000, position=2048]]
+ getPosition(1404000) = [[timeUs=1320000, position=18896], [timeUs=1440000, position=20528]]
+ getPosition(2808000) = [[timeUs=2760000, position=38168]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 25920
+ sample count = 82
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 1:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 2:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 3:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 4:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 5:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 6:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 7:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 8:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 9:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 10:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 11:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 12:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 13:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 14:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 15:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 16:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 17:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 18:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 19:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 20:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 21:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 22:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 23:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 24:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 25:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 26:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 27:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 28:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 29:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 30:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 31:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 32:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 33:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 34:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 35:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 36:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 37:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 38:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 39:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 40:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 41:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 42:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 43:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 44:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 45:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 46:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 47:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 48:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 49:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 50:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 51:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 52:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 53:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 54:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 55:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 56:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 57:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 58:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 59:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 60:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 61:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 62:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 63:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 64:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 65:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 66:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 67:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 68:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 69:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 70:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 71:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 72:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 73:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 74:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 75:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 76:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 77:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 78:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 79:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 80:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 81:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.2.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.2.dump
new file mode 100644
index 0000000..32a412e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.2.dump
@@ -0,0 +1,185 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=224]]
+ getPosition(1) = [[timeUs=0, position=224], [timeUs=120000, position=2048]]
+ getPosition(1404000) = [[timeUs=1320000, position=18896], [timeUs=1440000, position=20528]]
+ getPosition(2808000) = [[timeUs=2760000, position=38168]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 12624
+ sample count = 42
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 1:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 2:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 3:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 4:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 5:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 6:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 7:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 8:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 9:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 10:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 11:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 12:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 13:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 14:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 15:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 16:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 17:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 18:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 19:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 20:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 21:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 22:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 23:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 24:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 25:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 26:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 27:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 28:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 29:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 30:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 31:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 32:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 33:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 34:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 35:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 36:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 37:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 38:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 39:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 40:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 41:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.3.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.3.dump
new file mode 100644
index 0000000..41e499b
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.3.dump
@@ -0,0 +1,25 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=224]]
+ getPosition(1) = [[timeUs=0, position=224], [timeUs=120000, position=2048]]
+ getPosition(1404000) = [[timeUs=1320000, position=18896], [timeUs=1440000, position=20528]]
+ getPosition(2808000) = [[timeUs=2760000, position=38168]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216
+ sample count = 2
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 1:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.unknown_length.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.unknown_length.dump
new file mode 100644
index 0000000..90edd28
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-no-seek-table.mp3.unknown_length.dump
@@ -0,0 +1,485 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=224]]
+ getPosition(1) = [[timeUs=0, position=224], [timeUs=120000, position=2048]]
+ getPosition(1404000) = [[timeUs=1320000, position=18896], [timeUs=1440000, position=20528]]
+ getPosition(2808000) = [[timeUs=2760000, position=38168]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3 b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3
new file mode 100644
index 0000000..8b32dcd
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.0.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.0.dump
new file mode 100644
index 0000000..20a69e3
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.0.dump
@@ -0,0 +1,488 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=237]]
+ getPosition(1) = [[timeUs=1, position=237]]
+ getPosition(1404000) = [[timeUs=1404000, position=20120]]
+ getPosition(2808000) = [[timeUs=2808000, position=38396]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TSSE: description=null: value=Lavf58.29.100]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.1.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.1.dump
new file mode 100644
index 0000000..ef3785b
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.1.dump
@@ -0,0 +1,328 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=237]]
+ getPosition(1) = [[timeUs=1, position=237]]
+ getPosition(1404000) = [[timeUs=1404000, position=20120]]
+ getPosition(2808000) = [[timeUs=2808000, position=38396]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 24384
+ sample count = 77
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TSSE: description=null: value=Lavf58.29.100]
+ sample 0:
+ time = 958041
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 1:
+ time = 982041
+ flags = 1
+ data = length 336, hash D4160463
+ sample 2:
+ time = 1006041
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 3:
+ time = 1030041
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 4:
+ time = 1054041
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 5:
+ time = 1078041
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 6:
+ time = 1102041
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 7:
+ time = 1126041
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 8:
+ time = 1150041
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 9:
+ time = 1174041
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 10:
+ time = 1198041
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 11:
+ time = 1222041
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 12:
+ time = 1246041
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 13:
+ time = 1270041
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 14:
+ time = 1294041
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 15:
+ time = 1318041
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 16:
+ time = 1342041
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 17:
+ time = 1366041
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 18:
+ time = 1390041
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 19:
+ time = 1414041
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 20:
+ time = 1438041
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 21:
+ time = 1462041
+ flags = 1
+ data = length 336, hash 8868086
+ sample 22:
+ time = 1486041
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 23:
+ time = 1510041
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 24:
+ time = 1534041
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 25:
+ time = 1558041
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 26:
+ time = 1582041
+ flags = 1
+ data = length 288, hash D54C970
+ sample 27:
+ time = 1606041
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 28:
+ time = 1630041
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 29:
+ time = 1654041
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 30:
+ time = 1678041
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 31:
+ time = 1702041
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 32:
+ time = 1726041
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 33:
+ time = 1750041
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 34:
+ time = 1774041
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 35:
+ time = 1798041
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 36:
+ time = 1822041
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 37:
+ time = 1846041
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 38:
+ time = 1870041
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 39:
+ time = 1894041
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 40:
+ time = 1918041
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 41:
+ time = 1942041
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 42:
+ time = 1966041
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 43:
+ time = 1990041
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 44:
+ time = 2014041
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 45:
+ time = 2038041
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 46:
+ time = 2062041
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 47:
+ time = 2086041
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 48:
+ time = 2110041
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 49:
+ time = 2134041
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 50:
+ time = 2158041
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 51:
+ time = 2182041
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 52:
+ time = 2206041
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 53:
+ time = 2230041
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 54:
+ time = 2254041
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 55:
+ time = 2278041
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 56:
+ time = 2302041
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 57:
+ time = 2326041
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 58:
+ time = 2350041
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 59:
+ time = 2374041
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 60:
+ time = 2398041
+ flags = 1
+ data = length 336, hash E0135533
+ sample 61:
+ time = 2422041
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 62:
+ time = 2446041
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 63:
+ time = 2470041
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 64:
+ time = 2494041
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 65:
+ time = 2518041
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 66:
+ time = 2542041
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 67:
+ time = 2566041
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 68:
+ time = 2590041
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 69:
+ time = 2614041
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 70:
+ time = 2638041
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 71:
+ time = 2662041
+ flags = 1
+ data = length 240, hash E649682F
+ sample 72:
+ time = 2686041
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 73:
+ time = 2710041
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 74:
+ time = 2734041
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 75:
+ time = 2758041
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 76:
+ time = 2782041
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.2.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.2.dump
new file mode 100644
index 0000000..12697f6
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.2.dump
@@ -0,0 +1,172 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=237]]
+ getPosition(1) = [[timeUs=1, position=237]]
+ getPosition(1404000) = [[timeUs=1404000, position=20120]]
+ getPosition(2808000) = [[timeUs=2808000, position=38396]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 11328
+ sample count = 38
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TSSE: description=null: value=Lavf58.29.100]
+ sample 0:
+ time = 1886772
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 1:
+ time = 1910772
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 2:
+ time = 1934772
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 3:
+ time = 1958772
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 4:
+ time = 1982772
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 5:
+ time = 2006772
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 6:
+ time = 2030772
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 7:
+ time = 2054772
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 8:
+ time = 2078772
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 9:
+ time = 2102772
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 10:
+ time = 2126772
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 11:
+ time = 2150772
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 12:
+ time = 2174772
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 13:
+ time = 2198772
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 14:
+ time = 2222772
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 15:
+ time = 2246772
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 16:
+ time = 2270772
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 17:
+ time = 2294772
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 18:
+ time = 2318772
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 19:
+ time = 2342772
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 20:
+ time = 2366772
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 21:
+ time = 2390772
+ flags = 1
+ data = length 336, hash E0135533
+ sample 22:
+ time = 2414772
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 23:
+ time = 2438772
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 24:
+ time = 2462772
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 25:
+ time = 2486772
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 26:
+ time = 2510772
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 27:
+ time = 2534772
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 28:
+ time = 2558772
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 29:
+ time = 2582772
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 30:
+ time = 2606772
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 31:
+ time = 2630772
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 32:
+ time = 2654772
+ flags = 1
+ data = length 240, hash E649682F
+ sample 33:
+ time = 2678772
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 34:
+ time = 2702772
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 35:
+ time = 2726772
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 36:
+ time = 2750772
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 37:
+ time = 2774772
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.3.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.3.dump
new file mode 100644
index 0000000..2ab479e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.3.dump
@@ -0,0 +1,20 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=237]]
+ getPosition(1) = [[timeUs=1, position=237]]
+ getPosition(1404000) = [[timeUs=1404000, position=20120]]
+ getPosition(2808000) = [[timeUs=2808000, position=38396]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TSSE: description=null: value=Lavf58.29.100]
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.unknown_length.dump b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.unknown_length.dump
new file mode 100644
index 0000000..20a69e3
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-vbr-xing-header.mp3.unknown_length.dump
@@ -0,0 +1,488 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=237]]
+ getPosition(1) = [[timeUs=1, position=237]]
+ getPosition(1404000) = [[timeUs=1404000, position=20120]]
+ getPosition(2808000) = [[timeUs=2808000, position=38396]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TSSE: description=null: value=Lavf58.29.100]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3 b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp3/play-trimmed.mp3
rename to tree/testdata/src/test/assets/mp3/play-trimmed.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.0.dump b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.0.dump
new file mode 100644
index 0000000..0fd5622
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.0.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 26125
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0]]
+ getPosition(13062) = [[timeUs=0, position=0]]
+ getPosition(26125) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 418
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 418, hash B819987
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.1.dump b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.1.dump
new file mode 100644
index 0000000..0fd5622
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.1.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 26125
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0]]
+ getPosition(13062) = [[timeUs=0, position=0]]
+ getPosition(26125) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 418
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 418, hash B819987
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.2.dump b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.2.dump
new file mode 100644
index 0000000..0fd5622
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.2.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 26125
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0]]
+ getPosition(13062) = [[timeUs=0, position=0]]
+ getPosition(26125) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 418
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 418, hash B819987
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.3.dump b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.3.dump
new file mode 100644
index 0000000..0fd5622
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.3.dump
@@ -0,0 +1,21 @@
+seekMap:
+ isSeekable = true
+ duration = 26125
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0]]
+ getPosition(13062) = [[timeUs=0, position=0]]
+ getPosition(26125) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 418
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 418, hash B819987
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.unknown_length.dump b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.unknown_length.dump
new file mode 100644
index 0000000..2e747d9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/play-trimmed.mp3.unknown_length.dump
@@ -0,0 +1,18 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 418
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 418, hash B819987
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/midroll-5s.mp4 b/tree/testdata/src/test/assets/mp4/midroll-5s.mp4
new file mode 100644
index 0000000..b7f574a
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/midroll-5s.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/postroll-5s.mp4 b/tree/testdata/src/test/assets/mp4/postroll-5s.mp4
new file mode 100644
index 0000000..23057d5
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/postroll-5s.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/preroll-5s.mp4 b/tree/testdata/src/test/assets/mp4/preroll-5s.mp4
new file mode 100644
index 0000000..d2e63d0
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/preroll-5s.mp4
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/mp4/sample.mp4 b/tree/testdata/src/test/assets/mp4/sample.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample.mp4
rename to tree/testdata/src/test/assets/mp4/sample.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample.mp4.0.dump
new file mode 100644
index 0000000..c1d6530
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample.mp4.0.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=48]]
+ getPosition(1) = [[timeUs=0, position=48]]
+ getPosition(512000) = [[timeUs=0, position=48]]
+ getPosition(1024000) = [[timeUs=0, position=48]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 44000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 67219
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 90439
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 113659
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 136879
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 160099
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 183319
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 206539
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 229759
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 252979
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 276199
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 299419
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample.mp4.1.dump
new file mode 100644
index 0000000..0cba489
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample.mp4.1.dump
@@ -0,0 +1,288 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=48]]
+ getPosition(1) = [[timeUs=0, position=48]]
+ getPosition(512000) = [[timeUs=0, position=48]]
+ getPosition(1024000) = [[timeUs=0, position=48]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 7464
+ sample count = 33
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 1:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 2:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 3:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 4:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 5:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 6:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 7:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 8:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 9:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 10:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 11:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 12:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 13:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 14:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 15:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 16:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 17:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 18:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 19:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 20:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 21:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 22:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 23:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 24:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 25:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 26:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 27:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 28:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 29:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 30:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 31:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 32:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample.mp4.2.dump
new file mode 100644
index 0000000..453c167
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample.mp4.2.dump
@@ -0,0 +1,228 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=48]]
+ getPosition(1) = [[timeUs=0, position=48]]
+ getPosition(512000) = [[timeUs=0, position=48]]
+ getPosition(1024000) = [[timeUs=0, position=48]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 4019
+ sample count = 18
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 1:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 2:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 3:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 4:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 5:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 6:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 7:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 8:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 9:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 10:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 11:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 12:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 13:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 14:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 15:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 16:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 17:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample.mp4.3.dump
new file mode 100644
index 0000000..0fe653a
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample.mp4.3.dump
@@ -0,0 +1,168 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=48]]
+ getPosition(1) = [[timeUs=0, position=48]]
+ getPosition(512000) = [[timeUs=0, position=48]]
+ getPosition(1024000) = [[timeUs=0, position=48]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 470
+ sample count = 3
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 1:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 2:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample.mp4.unknown_length.dump
new file mode 100644
index 0000000..c1d6530
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample.mp4.unknown_length.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=48]]
+ getPosition(1) = [[timeUs=0, position=48]]
+ getPosition(512000) = [[timeUs=0, position=48]]
+ getPosition(1024000) = [[timeUs=0, position=48]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 44000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 67219
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 90439
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 113659
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 136879
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 160099
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 183319
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 206539
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 229759
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 252979
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 276199
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 299419
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4 b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4
new file mode 100644
index 0000000..2ec7881
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.0.dump
new file mode 100644
index 0000000..c2e51fa
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.0.dump
@@ -0,0 +1,55 @@
+seekMap:
+ isSeekable = true
+ duration = 288333
+ getPosition(0) = [[timeUs=0, position=609]]
+ getPosition(1) = [[timeUs=1, position=609]]
+ getPosition(144166) = [[timeUs=144166, position=6753]]
+ getPosition(288333) = [[timeUs=288333, position=12897]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13824
+ sample count = 9
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ maxInputSize = 1566
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 8:
+ time = 256000
+ flags = 536870913
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.1.dump
new file mode 100644
index 0000000..80f0790
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.1.dump
@@ -0,0 +1,43 @@
+seekMap:
+ isSeekable = true
+ duration = 288333
+ getPosition(0) = [[timeUs=0, position=609]]
+ getPosition(1) = [[timeUs=1, position=609]]
+ getPosition(144166) = [[timeUs=144166, position=6753]]
+ getPosition(288333) = [[timeUs=288333, position=12897]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 9216
+ sample count = 6
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ maxInputSize = 1566
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 1:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 2:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 3:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 4:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 5:
+ time = 256000
+ flags = 536870913
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.2.dump
new file mode 100644
index 0000000..a8d1588
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.2.dump
@@ -0,0 +1,31 @@
+seekMap:
+ isSeekable = true
+ duration = 288333
+ getPosition(0) = [[timeUs=0, position=609]]
+ getPosition(1) = [[timeUs=1, position=609]]
+ getPosition(144166) = [[timeUs=144166, position=6753]]
+ getPosition(288333) = [[timeUs=288333, position=12897]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4608
+ sample count = 3
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ maxInputSize = 1566
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 1:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 2:
+ time = 256000
+ flags = 536870913
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.3.dump
new file mode 100644
index 0000000..17bf79c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 288333
+ getPosition(0) = [[timeUs=0, position=609]]
+ getPosition(1) = [[timeUs=1, position=609]]
+ getPosition(144166) = [[timeUs=144166, position=6753]]
+ getPosition(288333) = [[timeUs=288333, position=12897]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 1536
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ maxInputSize = 1566
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 256000
+ flags = 536870913
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.unknown_length.dump
new file mode 100644
index 0000000..c2e51fa
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3.mp4.unknown_length.dump
@@ -0,0 +1,55 @@
+seekMap:
+ isSeekable = true
+ duration = 288333
+ getPosition(0) = [[timeUs=0, position=609]]
+ getPosition(1) = [[timeUs=1, position=609]]
+ getPosition(144166) = [[timeUs=144166, position=6753]]
+ getPosition(288333) = [[timeUs=288333, position=12897]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13824
+ sample count = 9
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ maxInputSize = 1566
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 8:
+ time = 256000
+ flags = 536870913
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4 b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4
new file mode 100644
index 0000000..d8b0a6c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.0.dump
new file mode 100644
index 0000000..3724592
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.0.dump
@@ -0,0 +1,54 @@
+seekMap:
+ isSeekable = true
+ duration = 288000
+ getPosition(0) = [[timeUs=0, position=636]]
+ getPosition(1) = [[timeUs=0, position=636]]
+ getPosition(144000) = [[timeUs=0, position=636]]
+ getPosition(288000) = [[timeUs=0, position=636]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13824
+ sample count = 9
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.1.dump
new file mode 100644
index 0000000..e9019d4
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.1.dump
@@ -0,0 +1,46 @@
+seekMap:
+ isSeekable = true
+ duration = 288000
+ getPosition(0) = [[timeUs=0, position=636]]
+ getPosition(1) = [[timeUs=0, position=636]]
+ getPosition(144000) = [[timeUs=0, position=636]]
+ getPosition(288000) = [[timeUs=0, position=636]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 10752
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 1:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 2:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 3:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 4:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 5:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 6:
+ time = 256000
+ flags = 1
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.2.dump
new file mode 100644
index 0000000..2b9cb1c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.2.dump
@@ -0,0 +1,34 @@
+seekMap:
+ isSeekable = true
+ duration = 288000
+ getPosition(0) = [[timeUs=0, position=636]]
+ getPosition(1) = [[timeUs=0, position=636]]
+ getPosition(144000) = [[timeUs=0, position=636]]
+ getPosition(288000) = [[timeUs=0, position=636]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 6144
+ sample count = 4
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 1:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 2:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.3.dump
new file mode 100644
index 0000000..eb313f9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.3.dump
@@ -0,0 +1,22 @@
+seekMap:
+ isSeekable = true
+ duration = 288000
+ getPosition(0) = [[timeUs=0, position=636]]
+ getPosition(1) = [[timeUs=0, position=636]]
+ getPosition(144000) = [[timeUs=0, position=636]]
+ getPosition(288000) = [[timeUs=0, position=636]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 1536
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 256000
+ flags = 1
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.unknown_length.dump
new file mode 100644
index 0000000..3724592
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac3_fragmented.mp4.unknown_length.dump
@@ -0,0 +1,54 @@
+seekMap:
+ isSeekable = true
+ duration = 288000
+ getPosition(0) = [[timeUs=0, position=636]]
+ getPosition(1) = [[timeUs=0, position=636]]
+ getPosition(144000) = [[timeUs=0, position=636]]
+ getPosition(288000) = [[timeUs=0, position=636]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13824
+ sample count = 9
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 1536, hash AAD9FCD2
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4 b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_ac4.mp4
rename to tree/testdata/src/test/assets/mp4/sample_ac4.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.0.dump
new file mode 100644
index 0000000..8a8abf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.0.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=758]]
+ getPosition(1) = [[timeUs=1, position=758]]
+ getPosition(380000) = [[timeUs=380000, position=758]]
+ getPosition(760000) = [[timeUs=760000, position=758]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ maxInputSize = 622
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 0
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 0
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 0
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 0
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 0
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 0
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 0
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 0
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 0
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 0
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 0
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 0
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 519999
+ flags = 0
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 0
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 0
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 0
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 0
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 536870912
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.1.dump
new file mode 100644
index 0000000..8a8abf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.1.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=758]]
+ getPosition(1) = [[timeUs=1, position=758]]
+ getPosition(380000) = [[timeUs=380000, position=758]]
+ getPosition(760000) = [[timeUs=760000, position=758]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ maxInputSize = 622
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 0
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 0
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 0
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 0
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 0
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 0
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 0
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 0
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 0
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 0
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 0
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 0
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 519999
+ flags = 0
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 0
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 0
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 0
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 0
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 536870912
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.2.dump
new file mode 100644
index 0000000..8a8abf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.2.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=758]]
+ getPosition(1) = [[timeUs=1, position=758]]
+ getPosition(380000) = [[timeUs=380000, position=758]]
+ getPosition(760000) = [[timeUs=760000, position=758]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ maxInputSize = 622
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 0
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 0
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 0
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 0
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 0
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 0
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 0
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 0
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 0
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 0
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 0
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 0
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 519999
+ flags = 0
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 0
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 0
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 0
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 0
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 536870912
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.3.dump
new file mode 100644
index 0000000..8a8abf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.3.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=758]]
+ getPosition(1) = [[timeUs=1, position=758]]
+ getPosition(380000) = [[timeUs=380000, position=758]]
+ getPosition(760000) = [[timeUs=760000, position=758]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ maxInputSize = 622
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 0
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 0
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 0
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 0
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 0
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 0
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 0
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 0
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 0
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 0
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 0
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 0
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 519999
+ flags = 0
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 0
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 0
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 0
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 0
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 536870912
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.unknown_length.dump
new file mode 100644
index 0000000..8a8abf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4.mp4.unknown_length.dump
@@ -0,0 +1,95 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=758]]
+ getPosition(1) = [[timeUs=1, position=758]]
+ getPosition(380000) = [[timeUs=380000, position=758]]
+ getPosition(760000) = [[timeUs=760000, position=758]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ maxInputSize = 622
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 0
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 0
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 0
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 0
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 0
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 0
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 0
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 0
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 0
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 0
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 0
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 0
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 519999
+ flags = 0
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 0
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 0
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 0
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 0
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 536870912
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4 b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_ac4_fragmented.mp4
rename to tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump
new file mode 100644
index 0000000..b2c54a9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.0.dump
@@ -0,0 +1,94 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=685]]
+ getPosition(1) = [[timeUs=0, position=685]]
+ getPosition(380000) = [[timeUs=0, position=685]]
+ getPosition(760000) = [[timeUs=0, position=685]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump
new file mode 100644
index 0000000..f8d4804
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.1.dump
@@ -0,0 +1,70 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=685]]
+ getPosition(1) = [[timeUs=0, position=685]]
+ getPosition(380000) = [[timeUs=0, position=685]]
+ getPosition(760000) = [[timeUs=0, position=685]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 5411
+ sample count = 13
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 240000
+ flags = 1
+ data = length 367, hash CE558362
+ sample 1:
+ time = 280000
+ flags = 1
+ data = length 367, hash 51AD3043
+ sample 2:
+ time = 320000
+ flags = 1
+ data = length 367, hash EB72E95B
+ sample 3:
+ time = 360000
+ flags = 1
+ data = length 367, hash 47F8FF23
+ sample 4:
+ time = 400000
+ flags = 1
+ data = length 367, hash 8133883D
+ sample 5:
+ time = 440000
+ flags = 1
+ data = length 495, hash E14BDFEE
+ sample 6:
+ time = 480000
+ flags = 1
+ data = length 520, hash FEE56928
+ sample 7:
+ time = 520000
+ flags = 1
+ data = length 599, hash 41F496C5
+ sample 8:
+ time = 560000
+ flags = 1
+ data = length 436, hash 76D6404
+ sample 9:
+ time = 600000
+ flags = 1
+ data = length 366, hash 56D49D4D
+ sample 10:
+ time = 640000
+ flags = 1
+ data = length 393, hash 822FC8
+ sample 11:
+ time = 680000
+ flags = 1
+ data = length 374, hash FA8AE217
+ sample 12:
+ time = 720000
+ flags = 1
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump
new file mode 100644
index 0000000..4375173
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.2.dump
@@ -0,0 +1,46 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=685]]
+ getPosition(1) = [[timeUs=0, position=685]]
+ getPosition(380000) = [[timeUs=0, position=685]]
+ getPosition(760000) = [[timeUs=0, position=685]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3081
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 480000
+ flags = 1
+ data = length 520, hash FEE56928
+ sample 1:
+ time = 520000
+ flags = 1
+ data = length 599, hash 41F496C5
+ sample 2:
+ time = 560000
+ flags = 1
+ data = length 436, hash 76D6404
+ sample 3:
+ time = 600000
+ flags = 1
+ data = length 366, hash 56D49D4D
+ sample 4:
+ time = 640000
+ flags = 1
+ data = length 393, hash 822FC8
+ sample 5:
+ time = 680000
+ flags = 1
+ data = length 374, hash FA8AE217
+ sample 6:
+ time = 720000
+ flags = 1
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump
new file mode 100644
index 0000000..120dd66
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.3.dump
@@ -0,0 +1,22 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=685]]
+ getPosition(1) = [[timeUs=0, position=685]]
+ getPosition(380000) = [[timeUs=0, position=685]]
+ getPosition(760000) = [[timeUs=0, position=685]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 393
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 720000
+ flags = 1
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.unknown_length.dump
new file mode 100644
index 0000000..b2c54a9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_fragmented.mp4.unknown_length.dump
@@ -0,0 +1,94 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=685]]
+ getPosition(1) = [[timeUs=0, position=685]]
+ getPosition(380000) = [[timeUs=0, position=685]]
+ getPosition(760000) = [[timeUs=0, position=685]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7613
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 367, hash D2762FA
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 367, hash BDD3224A
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 367, hash 9302227B
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 367, hash 72996003
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 367, hash 88AE5A1B
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 367, hash E5346FE3
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 367, hash CE558362
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 367, hash 51AD3043
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 367, hash EB72E95B
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 367, hash 47F8FF23
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 367, hash 8133883D
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 495, hash E14BDFEE
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 520, hash FEE56928
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 599, hash 41F496C5
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 436, hash 76D6404
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 366, hash 56D49D4D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 393, hash 822FC8
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 374, hash FA8AE217
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 393, hash 8506A1B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4 b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_ac4_protected.mp4
rename to tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump
new file mode 100644
index 0000000..425e0f6
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.0.dump
@@ -0,0 +1,133 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=950]]
+ getPosition(1) = [[timeUs=0, position=950]]
+ getPosition(380000) = [[timeUs=0, position=950]]
+ getPosition(760000) = [[timeUs=0, position=950]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7936
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ drmInitData = -1683793742
+ sample 0:
+ time = 0
+ flags = 1073741825
+ data = length 384, hash 96EFFFF3
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 1:
+ time = 40000
+ flags = 1073741825
+ data = length 384, hash 899279C6
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 2:
+ time = 80000
+ flags = 1073741825
+ data = length 384, hash 9EA9F45
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 3:
+ time = 120000
+ flags = 1073741825
+ data = length 384, hash 82D362A9
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 4:
+ time = 160000
+ flags = 1073741825
+ data = length 384, hash B8705CFB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 5:
+ time = 200000
+ flags = 1073741825
+ data = length 384, hash 58B5628E
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 6:
+ time = 240000
+ flags = 1073741825
+ data = length 384, hash 87F3C13B
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 7:
+ time = 280000
+ flags = 1073741825
+ data = length 384, hash 54333DC5
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 8:
+ time = 320000
+ flags = 1073741825
+ data = length 384, hash 1C49C4B3
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 9:
+ time = 360000
+ flags = 1073741825
+ data = length 384, hash 5FDC324F
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 10:
+ time = 400000
+ flags = 1073741825
+ data = length 384, hash B2A7F444
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 11:
+ time = 440000
+ flags = 1073741825
+ data = length 512, hash 5FD06C1E
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 12:
+ time = 480000
+ flags = 1073741825
+ data = length 537, hash 7ABBDCB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 13:
+ time = 520000
+ flags = 1073741825
+ data = length 616, hash 3F657E23
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 14:
+ time = 560000
+ flags = 1073741825
+ data = length 453, hash 8FCF0529
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 15:
+ time = 600000
+ flags = 1073741825
+ data = length 383, hash 7F8C9E19
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 16:
+ time = 640000
+ flags = 1073741825
+ data = length 410, hash 3727858D
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 17:
+ time = 680000
+ flags = 1073741825
+ data = length 391, hash E2931212
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 18:
+ time = 720000
+ flags = 1073741825
+ data = length 410, hash 63017D46
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump
new file mode 100644
index 0000000..64935a8
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.1.dump
@@ -0,0 +1,97 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=950]]
+ getPosition(1) = [[timeUs=0, position=950]]
+ getPosition(380000) = [[timeUs=0, position=950]]
+ getPosition(760000) = [[timeUs=0, position=950]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 5632
+ sample count = 13
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ drmInitData = -1683793742
+ sample 0:
+ time = 240000
+ flags = 1073741825
+ data = length 384, hash 87F3C13B
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 1:
+ time = 280000
+ flags = 1073741825
+ data = length 384, hash 54333DC5
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 2:
+ time = 320000
+ flags = 1073741825
+ data = length 384, hash 1C49C4B3
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 3:
+ time = 360000
+ flags = 1073741825
+ data = length 384, hash 5FDC324F
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 4:
+ time = 400000
+ flags = 1073741825
+ data = length 384, hash B2A7F444
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 5:
+ time = 440000
+ flags = 1073741825
+ data = length 512, hash 5FD06C1E
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 6:
+ time = 480000
+ flags = 1073741825
+ data = length 537, hash 7ABBDCB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 7:
+ time = 520000
+ flags = 1073741825
+ data = length 616, hash 3F657E23
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 8:
+ time = 560000
+ flags = 1073741825
+ data = length 453, hash 8FCF0529
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 9:
+ time = 600000
+ flags = 1073741825
+ data = length 383, hash 7F8C9E19
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 10:
+ time = 640000
+ flags = 1073741825
+ data = length 410, hash 3727858D
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 11:
+ time = 680000
+ flags = 1073741825
+ data = length 391, hash E2931212
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 12:
+ time = 720000
+ flags = 1073741825
+ data = length 410, hash 63017D46
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump
new file mode 100644
index 0000000..5fad73c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.2.dump
@@ -0,0 +1,61 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=950]]
+ getPosition(1) = [[timeUs=0, position=950]]
+ getPosition(380000) = [[timeUs=0, position=950]]
+ getPosition(760000) = [[timeUs=0, position=950]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 3200
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ drmInitData = -1683793742
+ sample 0:
+ time = 480000
+ flags = 1073741825
+ data = length 537, hash 7ABBDCB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 1:
+ time = 520000
+ flags = 1073741825
+ data = length 616, hash 3F657E23
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 2:
+ time = 560000
+ flags = 1073741825
+ data = length 453, hash 8FCF0529
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 3:
+ time = 600000
+ flags = 1073741825
+ data = length 383, hash 7F8C9E19
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 4:
+ time = 640000
+ flags = 1073741825
+ data = length 410, hash 3727858D
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 5:
+ time = 680000
+ flags = 1073741825
+ data = length 391, hash E2931212
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 6:
+ time = 720000
+ flags = 1073741825
+ data = length 410, hash 63017D46
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump
new file mode 100644
index 0000000..3a857ff
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.3.dump
@@ -0,0 +1,25 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=950]]
+ getPosition(1) = [[timeUs=0, position=950]]
+ getPosition(380000) = [[timeUs=0, position=950]]
+ getPosition(760000) = [[timeUs=0, position=950]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 410
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ drmInitData = -1683793742
+ sample 0:
+ time = 720000
+ flags = 1073741825
+ data = length 410, hash 63017D46
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.unknown_length.dump
new file mode 100644
index 0000000..425e0f6
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_ac4_protected.mp4.unknown_length.dump
@@ -0,0 +1,133 @@
+seekMap:
+ isSeekable = true
+ duration = 760000
+ getPosition(0) = [[timeUs=0, position=950]]
+ getPosition(1) = [[timeUs=0, position=950]]
+ getPosition(380000) = [[timeUs=0, position=950]]
+ getPosition(760000) = [[timeUs=0, position=950]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7936
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ language = und
+ drmInitData = -1683793742
+ sample 0:
+ time = 0
+ flags = 1073741825
+ data = length 384, hash 96EFFFF3
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 1:
+ time = 40000
+ flags = 1073741825
+ data = length 384, hash 899279C6
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 2:
+ time = 80000
+ flags = 1073741825
+ data = length 384, hash 9EA9F45
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 3:
+ time = 120000
+ flags = 1073741825
+ data = length 384, hash 82D362A9
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 4:
+ time = 160000
+ flags = 1073741825
+ data = length 384, hash B8705CFB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 5:
+ time = 200000
+ flags = 1073741825
+ data = length 384, hash 58B5628E
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 6:
+ time = 240000
+ flags = 1073741825
+ data = length 384, hash 87F3C13B
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 7:
+ time = 280000
+ flags = 1073741825
+ data = length 384, hash 54333DC5
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 8:
+ time = 320000
+ flags = 1073741825
+ data = length 384, hash 1C49C4B3
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 9:
+ time = 360000
+ flags = 1073741825
+ data = length 384, hash 5FDC324F
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 10:
+ time = 400000
+ flags = 1073741825
+ data = length 384, hash B2A7F444
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 11:
+ time = 440000
+ flags = 1073741825
+ data = length 512, hash 5FD06C1E
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 12:
+ time = 480000
+ flags = 1073741825
+ data = length 537, hash 7ABBDCB
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 13:
+ time = 520000
+ flags = 1073741825
+ data = length 616, hash 3F657E23
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 14:
+ time = 560000
+ flags = 1073741825
+ data = length 453, hash 8FCF0529
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 15:
+ time = 600000
+ flags = 1073741825
+ data = length 383, hash 7F8C9E19
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 16:
+ time = 640000
+ flags = 1073741825
+ data = length 410, hash 3727858D
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 17:
+ time = 680000
+ flags = 1073741825
+ data = length 391, hash E2931212
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+ sample 18:
+ time = 720000
+ flags = 1073741825
+ data = length 410, hash 63017D46
+ crypto mode = 1
+ encryption key = length 16, hash 9FDDEA52
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4 b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_android_slow_motion.mp4
rename to tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump
new file mode 100644
index 0000000..e8b96af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.0.dump
@@ -0,0 +1,51 @@
+seekMap:
+ isSeekable = true
+ duration = 526000
+ getPosition(0) = [[timeUs=0, position=1161]]
+ getPosition(1) = [[timeUs=0, position=1161]]
+ getPosition(263000) = [[timeUs=0, position=1161]]
+ getPosition(526000) = [[timeUs=0, position=1161]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 42320
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 34686
+ width = 1280
+ height = 720
+ frameRate = 13.307984
+ metadata = entries=[mdta: key=com.android.capture.fps]
+ initializationData:
+ data = length 22, hash 4CF81805
+ data = length 9, hash FBAFBA1C
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 34656, hash D92B66FF
+ sample 1:
+ time = 325344
+ flags = 0
+ data = length 768, hash D0C3B229
+ sample 2:
+ time = 358677
+ flags = 0
+ data = length 1184, hash C598EFC0
+ sample 3:
+ time = 392011
+ flags = 0
+ data = length 576, hash 667AEC2C
+ sample 4:
+ time = 425344
+ flags = 0
+ data = length 1456, hash 430D1498
+ sample 5:
+ time = 458677
+ flags = 0
+ data = length 1280, hash 12267E0E
+ sample 6:
+ time = 492011
+ flags = 536870912
+ data = length 2400, hash FBCB42C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump
new file mode 100644
index 0000000..e8b96af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.1.dump
@@ -0,0 +1,51 @@
+seekMap:
+ isSeekable = true
+ duration = 526000
+ getPosition(0) = [[timeUs=0, position=1161]]
+ getPosition(1) = [[timeUs=0, position=1161]]
+ getPosition(263000) = [[timeUs=0, position=1161]]
+ getPosition(526000) = [[timeUs=0, position=1161]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 42320
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 34686
+ width = 1280
+ height = 720
+ frameRate = 13.307984
+ metadata = entries=[mdta: key=com.android.capture.fps]
+ initializationData:
+ data = length 22, hash 4CF81805
+ data = length 9, hash FBAFBA1C
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 34656, hash D92B66FF
+ sample 1:
+ time = 325344
+ flags = 0
+ data = length 768, hash D0C3B229
+ sample 2:
+ time = 358677
+ flags = 0
+ data = length 1184, hash C598EFC0
+ sample 3:
+ time = 392011
+ flags = 0
+ data = length 576, hash 667AEC2C
+ sample 4:
+ time = 425344
+ flags = 0
+ data = length 1456, hash 430D1498
+ sample 5:
+ time = 458677
+ flags = 0
+ data = length 1280, hash 12267E0E
+ sample 6:
+ time = 492011
+ flags = 536870912
+ data = length 2400, hash FBCB42C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump
new file mode 100644
index 0000000..e8b96af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.2.dump
@@ -0,0 +1,51 @@
+seekMap:
+ isSeekable = true
+ duration = 526000
+ getPosition(0) = [[timeUs=0, position=1161]]
+ getPosition(1) = [[timeUs=0, position=1161]]
+ getPosition(263000) = [[timeUs=0, position=1161]]
+ getPosition(526000) = [[timeUs=0, position=1161]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 42320
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 34686
+ width = 1280
+ height = 720
+ frameRate = 13.307984
+ metadata = entries=[mdta: key=com.android.capture.fps]
+ initializationData:
+ data = length 22, hash 4CF81805
+ data = length 9, hash FBAFBA1C
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 34656, hash D92B66FF
+ sample 1:
+ time = 325344
+ flags = 0
+ data = length 768, hash D0C3B229
+ sample 2:
+ time = 358677
+ flags = 0
+ data = length 1184, hash C598EFC0
+ sample 3:
+ time = 392011
+ flags = 0
+ data = length 576, hash 667AEC2C
+ sample 4:
+ time = 425344
+ flags = 0
+ data = length 1456, hash 430D1498
+ sample 5:
+ time = 458677
+ flags = 0
+ data = length 1280, hash 12267E0E
+ sample 6:
+ time = 492011
+ flags = 536870912
+ data = length 2400, hash FBCB42C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump
new file mode 100644
index 0000000..e8b96af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.3.dump
@@ -0,0 +1,51 @@
+seekMap:
+ isSeekable = true
+ duration = 526000
+ getPosition(0) = [[timeUs=0, position=1161]]
+ getPosition(1) = [[timeUs=0, position=1161]]
+ getPosition(263000) = [[timeUs=0, position=1161]]
+ getPosition(526000) = [[timeUs=0, position=1161]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 42320
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 34686
+ width = 1280
+ height = 720
+ frameRate = 13.307984
+ metadata = entries=[mdta: key=com.android.capture.fps]
+ initializationData:
+ data = length 22, hash 4CF81805
+ data = length 9, hash FBAFBA1C
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 34656, hash D92B66FF
+ sample 1:
+ time = 325344
+ flags = 0
+ data = length 768, hash D0C3B229
+ sample 2:
+ time = 358677
+ flags = 0
+ data = length 1184, hash C598EFC0
+ sample 3:
+ time = 392011
+ flags = 0
+ data = length 576, hash 667AEC2C
+ sample 4:
+ time = 425344
+ flags = 0
+ data = length 1456, hash 430D1498
+ sample 5:
+ time = 458677
+ flags = 0
+ data = length 1280, hash 12267E0E
+ sample 6:
+ time = 492011
+ flags = 536870912
+ data = length 2400, hash FBCB42C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.unknown_length.dump
new file mode 100644
index 0000000..e8b96af
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_android_slow_motion.mp4.unknown_length.dump
@@ -0,0 +1,51 @@
+seekMap:
+ isSeekable = true
+ duration = 526000
+ getPosition(0) = [[timeUs=0, position=1161]]
+ getPosition(1) = [[timeUs=0, position=1161]]
+ getPosition(263000) = [[timeUs=0, position=1161]]
+ getPosition(526000) = [[timeUs=0, position=1161]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 42320
+ sample count = 7
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 34686
+ width = 1280
+ height = 720
+ frameRate = 13.307984
+ metadata = entries=[mdta: key=com.android.capture.fps]
+ initializationData:
+ data = length 22, hash 4CF81805
+ data = length 9, hash FBAFBA1C
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 34656, hash D92B66FF
+ sample 1:
+ time = 325344
+ flags = 0
+ data = length 768, hash D0C3B229
+ sample 2:
+ time = 358677
+ flags = 0
+ data = length 1184, hash C598EFC0
+ sample 3:
+ time = 392011
+ flags = 0
+ data = length 576, hash 667AEC2C
+ sample 4:
+ time = 425344
+ flags = 0
+ data = length 1456, hash 430D1498
+ sample 5:
+ time = 458677
+ flags = 0
+ data = length 1280, hash 12267E0E
+ sample 6:
+ time = 492011
+ flags = 536870912
+ data = length 2400, hash FBCB42C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4 b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4
new file mode 100644
index 0000000..2bb1689
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.0.dump
new file mode 100644
index 0000000..8000864
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.0.dump
@@ -0,0 +1,235 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=898]]
+ getPosition(1) = [[timeUs=1, position=898]]
+ getPosition(864000) = [[timeUs=864000, position=108898]]
+ getPosition(1728000) = [[timeUs=1728000, position=212898]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ maxInputSize = 4030
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 1696000
+ flags = 536870913
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.1.dump
new file mode 100644
index 0000000..49ab3da
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.1.dump
@@ -0,0 +1,163 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=898]]
+ getPosition(1) = [[timeUs=1, position=898]]
+ getPosition(864000) = [[timeUs=864000, position=108898]]
+ getPosition(1728000) = [[timeUs=1728000, position=212898]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 144000
+ sample count = 36
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ maxInputSize = 4030
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 1:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 2:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 3:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 4:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 5:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 6:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 7:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 8:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 9:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 10:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 11:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 12:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 13:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 14:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 15:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 16:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 17:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 18:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 19:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 20:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 21:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 22:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 23:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 24:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 25:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 26:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 27:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 28:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 29:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 30:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 31:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 32:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 33:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 34:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 35:
+ time = 1696000
+ flags = 536870913
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.2.dump
new file mode 100644
index 0000000..19bfc7c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.2.dump
@@ -0,0 +1,91 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=898]]
+ getPosition(1) = [[timeUs=1, position=898]]
+ getPosition(864000) = [[timeUs=864000, position=108898]]
+ getPosition(1728000) = [[timeUs=1728000, position=212898]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 72000
+ sample count = 18
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ maxInputSize = 4030
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 1:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 2:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 3:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 4:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 5:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 6:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 7:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 8:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 9:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 10:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 11:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 12:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 13:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 14:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 15:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 16:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 17:
+ time = 1696000
+ flags = 536870913
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.3.dump
new file mode 100644
index 0000000..d34514d
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=898]]
+ getPosition(1) = [[timeUs=1, position=898]]
+ getPosition(864000) = [[timeUs=864000, position=108898]]
+ getPosition(1728000) = [[timeUs=1728000, position=212898]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4000
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ maxInputSize = 4030
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1696000
+ flags = 536870913
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.unknown_length.dump
new file mode 100644
index 0000000..8000864
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3.mp4.unknown_length.dump
@@ -0,0 +1,235 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=898]]
+ getPosition(1) = [[timeUs=1, position=898]]
+ getPosition(864000) = [[timeUs=864000, position=108898]]
+ getPosition(1728000) = [[timeUs=1728000, position=212898]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ maxInputSize = 4030
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 1696000
+ flags = 536870913
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4 b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4
new file mode 100644
index 0000000..ebd3698
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.0.dump
new file mode 100644
index 0000000..a7f3c63
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.0.dump
@@ -0,0 +1,234 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=638]]
+ getPosition(1) = [[timeUs=0, position=638]]
+ getPosition(864000) = [[timeUs=0, position=638]]
+ getPosition(1728000) = [[timeUs=0, position=638]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.1.dump
new file mode 100644
index 0000000..a627d00
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.1.dump
@@ -0,0 +1,166 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=638]]
+ getPosition(1) = [[timeUs=0, position=638]]
+ getPosition(864000) = [[timeUs=0, position=638]]
+ getPosition(1728000) = [[timeUs=0, position=638]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 148000
+ sample count = 37
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 544000
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 1:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 2:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 3:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 4:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 5:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 6:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 7:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 8:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 9:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 10:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 11:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 12:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 13:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 14:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 15:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 16:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 17:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 18:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 19:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 20:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 21:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 22:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 23:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 24:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 25:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 26:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 27:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 28:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 29:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 30:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 31:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 32:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 33:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 34:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 35:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 36:
+ time = 1696000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.2.dump
new file mode 100644
index 0000000..3101341
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.2.dump
@@ -0,0 +1,94 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=638]]
+ getPosition(1) = [[timeUs=0, position=638]]
+ getPosition(864000) = [[timeUs=0, position=638]]
+ getPosition(1728000) = [[timeUs=0, position=638]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 76000
+ sample count = 19
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 1:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 2:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 3:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 4:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 6:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 7:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 8:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 9:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 10:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 11:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 12:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 13:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 14:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 15:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 16:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 17:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 18:
+ time = 1696000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.3.dump
new file mode 100644
index 0000000..13ff558
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.3.dump
@@ -0,0 +1,22 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=638]]
+ getPosition(1) = [[timeUs=0, position=638]]
+ getPosition(864000) = [[timeUs=0, position=638]]
+ getPosition(1728000) = [[timeUs=0, position=638]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4000
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1696000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.unknown_length.dump
new file mode 100644
index 0000000..a7f3c63
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3_fragmented.mp4.unknown_length.dump
@@ -0,0 +1,234 @@
+seekMap:
+ isSeekable = true
+ duration = 1728000
+ getPosition(0) = [[timeUs=0, position=638]]
+ getPosition(1) = [[timeUs=0, position=638]]
+ getPosition(864000) = [[timeUs=0, position=638]]
+ getPosition(1728000) = [[timeUs=0, position=638]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4 b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4
new file mode 100644
index 0000000..cd1fdcc
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.0.dump
new file mode 100644
index 0000000..ecc28b7
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.0.dump
@@ -0,0 +1,275 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=956]]
+ getPosition(1) = [[timeUs=1, position=956]]
+ getPosition(1024000) = [[timeUs=1024000, position=82876]]
+ getPosition(2048000) = [[timeUs=2048000, position=162236]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ maxInputSize = 2590
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 536870913
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.1.dump
new file mode 100644
index 0000000..d9ed0c4
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.1.dump
@@ -0,0 +1,191 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=956]]
+ getPosition(1) = [[timeUs=1, position=956]]
+ getPosition(1024000) = [[timeUs=1024000, position=82876]]
+ getPosition(2048000) = [[timeUs=2048000, position=162236]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 110080
+ sample count = 43
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ maxInputSize = 2590
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 1:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 2:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 3:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 4:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 5:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 6:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 7:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 8:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 9:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 10:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 11:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 12:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 13:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 14:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 15:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 16:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 17:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 18:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 19:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 20:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 21:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 22:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 23:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 24:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 25:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 26:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 27:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 28:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 29:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 30:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 31:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 32:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 33:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 34:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 35:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 36:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 37:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 38:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 39:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 40:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 41:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 42:
+ time = 2016000
+ flags = 536870913
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.2.dump
new file mode 100644
index 0000000..741d519
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.2.dump
@@ -0,0 +1,107 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=956]]
+ getPosition(1) = [[timeUs=1, position=956]]
+ getPosition(1024000) = [[timeUs=1024000, position=82876]]
+ getPosition(2048000) = [[timeUs=2048000, position=162236]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 56320
+ sample count = 22
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ maxInputSize = 2590
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 1:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 2:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 3:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 4:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 5:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 6:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 7:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 8:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 9:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 10:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 11:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 12:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 13:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 14:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 15:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 16:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 17:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 18:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 19:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 20:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 21:
+ time = 2016000
+ flags = 536870913
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.3.dump
new file mode 100644
index 0000000..98fe8c7
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=956]]
+ getPosition(1) = [[timeUs=1, position=956]]
+ getPosition(1024000) = [[timeUs=1024000, position=82876]]
+ getPosition(2048000) = [[timeUs=2048000, position=162236]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2560
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ maxInputSize = 2590
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 2016000
+ flags = 536870913
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.unknown_length.dump
new file mode 100644
index 0000000..ecc28b7
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc.mp4.unknown_length.dump
@@ -0,0 +1,275 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=956]]
+ getPosition(1) = [[timeUs=1, position=956]]
+ getPosition(1024000) = [[timeUs=1024000, position=82876]]
+ getPosition(2048000) = [[timeUs=2048000, position=162236]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ maxInputSize = 2590
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 536870913
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4 b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4
new file mode 100644
index 0000000..fbe922c
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.0.dump
new file mode 100644
index 0000000..c5902f5
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.0.dump
@@ -0,0 +1,274 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=640]]
+ getPosition(1) = [[timeUs=0, position=640]]
+ getPosition(1024000) = [[timeUs=0, position=640]]
+ getPosition(2048000) = [[timeUs=0, position=640]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.1.dump
new file mode 100644
index 0000000..8fa0cbf
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.1.dump
@@ -0,0 +1,190 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=640]]
+ getPosition(1) = [[timeUs=0, position=640]]
+ getPosition(1024000) = [[timeUs=0, position=640]]
+ getPosition(2048000) = [[timeUs=0, position=640]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 110080
+ sample count = 43
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 1:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 2:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 3:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 4:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 5:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 6:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 7:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 8:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 9:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 10:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 11:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 12:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 13:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 14:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 15:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 16:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 17:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 18:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 19:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 20:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 21:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 22:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 23:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 24:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 25:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 26:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 27:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 28:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 29:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 30:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 31:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 32:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 33:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 34:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 35:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 36:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 37:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 38:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 39:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 40:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 41:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 42:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.2.dump
new file mode 100644
index 0000000..603ca0d
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.2.dump
@@ -0,0 +1,106 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=640]]
+ getPosition(1) = [[timeUs=0, position=640]]
+ getPosition(1024000) = [[timeUs=0, position=640]]
+ getPosition(2048000) = [[timeUs=0, position=640]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 56320
+ sample count = 22
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 1:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 2:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 3:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 4:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 5:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 6:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 7:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 8:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 9:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 10:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 11:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 12:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 13:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 14:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 15:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 16:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 17:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 18:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 19:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 20:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 21:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.3.dump
new file mode 100644
index 0000000..cd42dac
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.3.dump
@@ -0,0 +1,22 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=640]]
+ getPosition(1) = [[timeUs=0, position=640]]
+ getPosition(1024000) = [[timeUs=0, position=640]]
+ getPosition(2048000) = [[timeUs=0, position=640]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2560
+ sample count = 1
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump
new file mode 100644
index 0000000..c5902f5
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump
@@ -0,0 +1,274 @@
+seekMap:
+ isSeekable = true
+ duration = 2048000
+ getPosition(0) = [[timeUs=0, position=640]]
+ getPosition(1) = [[timeUs=0, position=640]]
+ getPosition(1024000) = [[timeUs=0, position=640]]
+ getPosition(2048000) = [[timeUs=0, position=640]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ language = und
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented.mp4 b/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_fragmented.mp4
rename to tree/testdata/src/test/assets/mp4/sample_fragmented.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.0.dump
new file mode 100644
index 0000000..a736207
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.0.dump
@@ -0,0 +1,333 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=1828]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.unknown_length.dump
new file mode 100644
index 0000000..a736207
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented.mp4.unknown_length.dump
@@ -0,0 +1,333 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=1828]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4 b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_fragmented_seekable.mp4
rename to tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
new file mode 100644
index 0000000..c879cde
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+ getPosition(1) = [[timeUs=66733, position=1325]]
+ getPosition(533866) = [[timeUs=66733, position=1325]]
+ getPosition(1067733) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
new file mode 100644
index 0000000..b264cf1
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
@@ -0,0 +1,276 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+ getPosition(1) = [[timeUs=66733, position=1325]]
+ getPosition(533866) = [[timeUs=66733, position=1325]]
+ getPosition(1067733) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 13359
+ sample count = 31
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 1:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 2:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 3:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 4:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 5:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 6:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 7:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 8:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 9:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 10:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 11:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 12:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 13:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 14:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 15:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 16:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 17:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 18:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 19:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 20:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 21:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 22:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 23:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 24:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 25:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 26:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 27:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 28:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 29:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 30:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
new file mode 100644
index 0000000..4b10cca
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
@@ -0,0 +1,216 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+ getPosition(1) = [[timeUs=66733, position=1325]]
+ getPosition(533866) = [[timeUs=66733, position=1325]]
+ getPosition(1067733) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 6804
+ sample count = 16
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 1:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 2:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 3:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 4:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 5:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 6:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 7:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 8:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 9:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 10:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 11:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 12:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 13:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 14:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 15:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
new file mode 100644
index 0000000..4477d48
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
@@ -0,0 +1,156 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+ getPosition(1) = [[timeUs=66733, position=1325]]
+ getPosition(533866) = [[timeUs=66733, position=1325]]
+ getPosition(1067733) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 10
+ sample count = 1
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.unknown_length.dump
new file mode 100644
index 0000000..c879cde
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_seekable.mp4.unknown_length.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+ getPosition(1) = [[timeUs=66733, position=1325]]
+ getPosition(533866) = [[timeUs=66733, position=1325]]
+ getPosition(1067733) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_fragmented_sei.mp4 b/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_fragmented_sei.mp4
rename to tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump
new file mode 100644
index 0000000..95fe467
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.0.dump
@@ -0,0 +1,338 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=1828]]
+numberOfTracks = 3
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+track 3:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.unknown_length.dump
new file mode 100644
index 0000000..95fe467
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_fragmented_sei.mp4.unknown_length.dump
@@ -0,0 +1,338 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=1828]]
+numberOfTracks = 3
+track 0:
+ total output bytes = 85933
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ width = 1080
+ height = 720
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ total output bytes = 18257
+ sample count = 46
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 5, hash 2B7623A
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+track 3:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4 b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4
similarity index 100%
rename from tree/library/extractor/src/test/assets/mp4/sample_mdat_too_long.mp4
rename to tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4
Binary files differ
diff --git a/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump
new file mode 100644
index 0000000..287d522
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.0.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=2192]]
+ getPosition(1) = [[timeUs=0, position=2192]]
+ getPosition(512000) = [[timeUs=0, position=2192]]
+ getPosition(1024000) = [[timeUs=0, position=2192]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 44000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 67219
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 90439
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 113659
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 136879
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 160099
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 183319
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 206539
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 229759
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 252979
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 276199
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 299419
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump
new file mode 100644
index 0000000..5c559c7
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.1.dump
@@ -0,0 +1,288 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=2192]]
+ getPosition(1) = [[timeUs=0, position=2192]]
+ getPosition(512000) = [[timeUs=0, position=2192]]
+ getPosition(1024000) = [[timeUs=0, position=2192]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 7464
+ sample count = 33
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 1:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 2:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 3:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 4:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 5:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 6:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 7:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 8:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 9:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 10:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 11:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 12:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 13:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 14:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 15:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 16:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 17:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 18:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 19:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 20:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 21:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 22:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 23:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 24:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 25:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 26:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 27:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 28:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 29:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 30:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 31:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 32:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump
new file mode 100644
index 0000000..34b0c20
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.2.dump
@@ -0,0 +1,228 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=2192]]
+ getPosition(1) = [[timeUs=0, position=2192]]
+ getPosition(512000) = [[timeUs=0, position=2192]]
+ getPosition(1024000) = [[timeUs=0, position=2192]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 4019
+ sample count = 18
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 1:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 2:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 3:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 4:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 5:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 6:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 7:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 8:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 9:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 10:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 11:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 12:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 13:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 14:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 15:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 16:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 17:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump
new file mode 100644
index 0000000..c1dc5e9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.3.dump
@@ -0,0 +1,168 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=2192]]
+ getPosition(1) = [[timeUs=0, position=2192]]
+ getPosition(512000) = [[timeUs=0, position=2192]]
+ getPosition(1024000) = [[timeUs=0, position=2192]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 470
+ sample count = 3
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 1:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 2:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.unknown_length.dump b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.unknown_length.dump
new file mode 100644
index 0000000..287d522
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp4/sample_mdat_too_long.mp4.unknown_length.dump
@@ -0,0 +1,336 @@
+seekMap:
+ isSeekable = true
+ duration = 1024000
+ getPosition(0) = [[timeUs=0, position=2192]]
+ getPosition(1) = [[timeUs=0, position=2192]]
+ getPosition(512000) = [[timeUs=0, position=2192]]
+ getPosition(1024000) = [[timeUs=0, position=2192]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 89876
+ sample count = 30
+ format 0:
+ id = 1
+ sampleMimeType = video/avc
+ maxInputSize = 36722
+ width = 1080
+ height = 720
+ frameRate = 29.970028
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 36692, hash D216076E
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 5312, hash D45D3CA0
+ sample 2:
+ time = 33366
+ flags = 0
+ data = length 599, hash 1BE7812D
+ sample 3:
+ time = 200200
+ flags = 0
+ data = length 7735, hash 4490F110
+ sample 4:
+ time = 133466
+ flags = 0
+ data = length 987, hash 560B5036
+ sample 5:
+ time = 100100
+ flags = 0
+ data = length 673, hash ED7CD8C7
+ sample 6:
+ time = 166833
+ flags = 0
+ data = length 523, hash 3020DF50
+ sample 7:
+ time = 333666
+ flags = 0
+ data = length 6061, hash 736C72B2
+ sample 8:
+ time = 266933
+ flags = 0
+ data = length 992, hash FE132F23
+ sample 9:
+ time = 233566
+ flags = 0
+ data = length 623, hash 5B2C1816
+ sample 10:
+ time = 300300
+ flags = 0
+ data = length 421, hash 742E69C1
+ sample 11:
+ time = 433766
+ flags = 0
+ data = length 4899, hash F72F86A1
+ sample 12:
+ time = 400400
+ flags = 0
+ data = length 568, hash 519A8E50
+ sample 13:
+ time = 367033
+ flags = 0
+ data = length 620, hash 3990AA39
+ sample 14:
+ time = 567233
+ flags = 0
+ data = length 5450, hash F06EC4AA
+ sample 15:
+ time = 500500
+ flags = 0
+ data = length 1051, hash 92DFA63A
+ sample 16:
+ time = 467133
+ flags = 0
+ data = length 874, hash 69587FB4
+ sample 17:
+ time = 533866
+ flags = 0
+ data = length 781, hash 36BE495B
+ sample 18:
+ time = 700700
+ flags = 0
+ data = length 4725, hash AC0C8CD3
+ sample 19:
+ time = 633966
+ flags = 0
+ data = length 1022, hash 5D8BFF34
+ sample 20:
+ time = 600600
+ flags = 0
+ data = length 790, hash 99413A99
+ sample 21:
+ time = 667333
+ flags = 0
+ data = length 610, hash 5E129290
+ sample 22:
+ time = 834166
+ flags = 0
+ data = length 2751, hash 769974CB
+ sample 23:
+ time = 767433
+ flags = 0
+ data = length 745, hash B78A477A
+ sample 24:
+ time = 734066
+ flags = 0
+ data = length 621, hash CF741E7A
+ sample 25:
+ time = 800800
+ flags = 0
+ data = length 505, hash 1DB4894E
+ sample 26:
+ time = 967633
+ flags = 0
+ data = length 1268, hash C15348DC
+ sample 27:
+ time = 900900
+ flags = 0
+ data = length 880, hash C2DE85D0
+ sample 28:
+ time = 867533
+ flags = 0
+ data = length 530, hash C98BC6A8
+ sample 29:
+ time = 934266
+ flags = 536870912
+ data = length 568, hash 4FE5C8EA
+track 1:
+ total output bytes = 9529
+ sample count = 45
+ format 0:
+ id = 2
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ maxInputSize = 294
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ metadata = entries=[TSSE: description=null: value=Lavf56.1.0]
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 44000
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 67219
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 2:
+ time = 90439
+ flags = 1
+ data = length 148, hash 894A176B
+ sample 3:
+ time = 113659
+ flags = 1
+ data = length 189, hash CEF235A1
+ sample 4:
+ time = 136879
+ flags = 1
+ data = length 205, hash BBF5F7B0
+ sample 5:
+ time = 160099
+ flags = 1
+ data = length 210, hash F278B193
+ sample 6:
+ time = 183319
+ flags = 1
+ data = length 210, hash 82DA1589
+ sample 7:
+ time = 206539
+ flags = 1
+ data = length 207, hash 5BE231DF
+ sample 8:
+ time = 229759
+ flags = 1
+ data = length 225, hash 18819EE1
+ sample 9:
+ time = 252979
+ flags = 1
+ data = length 215, hash CA7FA67B
+ sample 10:
+ time = 276199
+ flags = 1
+ data = length 211, hash 581A1C18
+ sample 11:
+ time = 299419
+ flags = 1
+ data = length 216, hash ADB88187
+ sample 12:
+ time = 322639
+ flags = 1
+ data = length 229, hash 2E8BA4DC
+ sample 13:
+ time = 345859
+ flags = 1
+ data = length 232, hash 22F0C510
+ sample 14:
+ time = 369079
+ flags = 1
+ data = length 235, hash 867AD0DC
+ sample 15:
+ time = 392299
+ flags = 1
+ data = length 231, hash 84E823A8
+ sample 16:
+ time = 415519
+ flags = 1
+ data = length 226, hash 1BEF3A95
+ sample 17:
+ time = 438739
+ flags = 1
+ data = length 216, hash EAA345AE
+ sample 18:
+ time = 461959
+ flags = 1
+ data = length 229, hash 6957411F
+ sample 19:
+ time = 485179
+ flags = 1
+ data = length 219, hash 41275022
+ sample 20:
+ time = 508399
+ flags = 1
+ data = length 241, hash 6495DF96
+ sample 21:
+ time = 531619
+ flags = 1
+ data = length 228, hash 63D95906
+ sample 22:
+ time = 554839
+ flags = 1
+ data = length 238, hash 34F676F9
+ sample 23:
+ time = 578058
+ flags = 1
+ data = length 234, hash E5CBC045
+ sample 24:
+ time = 601278
+ flags = 1
+ data = length 231, hash 5FC43661
+ sample 25:
+ time = 624498
+ flags = 1
+ data = length 217, hash 682708ED
+ sample 26:
+ time = 647718
+ flags = 1
+ data = length 239, hash D43780FC
+ sample 27:
+ time = 670938
+ flags = 1
+ data = length 243, hash C5E17980
+ sample 28:
+ time = 694158
+ flags = 1
+ data = length 231, hash AC5837BA
+ sample 29:
+ time = 717378
+ flags = 1
+ data = length 230, hash 169EE895
+ sample 30:
+ time = 740598
+ flags = 1
+ data = length 238, hash C48FF3F1
+ sample 31:
+ time = 763818
+ flags = 1
+ data = length 225, hash 531E4599
+ sample 32:
+ time = 787038
+ flags = 1
+ data = length 232, hash CB3C6B8D
+ sample 33:
+ time = 810258
+ flags = 1
+ data = length 243, hash F8C94C7
+ sample 34:
+ time = 833478
+ flags = 1
+ data = length 232, hash A646A7D0
+ sample 35:
+ time = 856698
+ flags = 1
+ data = length 237, hash E8B787A5
+ sample 36:
+ time = 879918
+ flags = 1
+ data = length 228, hash 3FA7A29F
+ sample 37:
+ time = 903138
+ flags = 1
+ data = length 235, hash B9B33B0A
+ sample 38:
+ time = 926358
+ flags = 1
+ data = length 264, hash 71A4869E
+ sample 39:
+ time = 949578
+ flags = 1
+ data = length 257, hash D049B54C
+ sample 40:
+ time = 972798
+ flags = 1
+ data = length 227, hash 66757231
+ sample 41:
+ time = 996018
+ flags = 1
+ data = length 227, hash BD374F1B
+ sample 42:
+ time = 1019238
+ flags = 1
+ data = length 235, hash 999477F6
+ sample 43:
+ time = 1042458
+ flags = 1
+ data = length 229, hash FFF98DF0
+ sample 44:
+ time = 1065678
+ flags = 536870913
+ data = length 6, hash 31B22286
+tracksEnded = true
diff --git a/tree/library/core/src/test/assets/mp4/testvid_1022ms.mp4 b/tree/testdata/src/test/assets/mp4/testvid_1022ms.mp4
similarity index 100%
rename from tree/library/core/src/test/assets/mp4/testvid_1022ms.mp4
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms.mp4
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/mp4/video000.png b/tree/testdata/src/test/assets/mp4/testvid_1022ms_000.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/mp4/video000.png
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms_000.png
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/mp4/video014.png b/tree/testdata/src/test/assets/mp4/testvid_1022ms_014.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/mp4/video014.png
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms_014.png
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/mp4/video015.png b/tree/testdata/src/test/assets/mp4/testvid_1022ms_015.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/mp4/video015.png
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms_015.png
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/mp4/video016.png b/tree/testdata/src/test/assets/mp4/testvid_1022ms_016.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/mp4/video016.png
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms_016.png
Binary files differ
diff --git a/tree/library/core/src/androidTest/assets/mp4/video029.png b/tree/testdata/src/test/assets/mp4/testvid_1022ms_029.png
similarity index 100%
rename from tree/library/core/src/androidTest/assets/mp4/video029.png
rename to tree/testdata/src/test/assets/mp4/testvid_1022ms_029.png
Binary files differ
diff --git a/tree/library/dash/src/test/assets/sample_mpd b/tree/testdata/src/test/assets/mpd/sample_mpd
similarity index 100%
rename from tree/library/dash/src/test/assets/sample_mpd
rename to tree/testdata/src/test/assets/mpd/sample_mpd
diff --git a/tree/testdata/src/test/assets/mpd/sample_mpd_asset_identifier b/tree/testdata/src/test/assets/mpd/sample_mpd_asset_identifier
new file mode 100644
index 0000000..ff5bc87
--- /dev/null
+++ b/tree/testdata/src/test/assets/mpd/sample_mpd_asset_identifier
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:yt="http://youtube.com/yt/2012/10/10" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="dynamic" availabilityStartTime="2016-10-14T17:00:17" timeShiftBufferDepth="PT7200.000S" minimumUpdatePeriod="PT2.000S" yt:earliestMediaSequence="0" yt:mpdRequestTime="2016-10-14T18:29:17.082" yt:mpdResponseTime="2016-10-14T18:29:17.194">
+ <Period start="PT0.000S" yt:segmentIngestTime="2016-10-14T17:00:14.257">
+ <AssetIdentifier schemeIdUri="urn:org:dashif:asset-id:2013" value="md:cid:EIDR:10.5240%2f0EFB-02CD-126E-8092-1E49-W" id="uniqueId"/>
+ <SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
+ <SegmentTimeline>
+ <S d="2002" t="6009" r="2"/>
+ </SegmentTimeline>
+ </SegmentTemplate>
+ <AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
+ <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
+ <Representation id="140" codecs="mp4a.40.2" audioSamplingRate="48000" startWithSAP="1" bandwidth="144000">
+ <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
+ <BaseURL>http://www.dummy.url/</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
+ <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
+ <Representation id="133" codecs="avc1.4d4015" width="426" height="240" startWithSAP="1" maxPlayoutRate="1" bandwidth="258000" frameRate="30">
+ <BaseURL>http://www.dummy.url/</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/tree/library/dash/src/test/assets/sample_mpd_event_stream b/tree/testdata/src/test/assets/mpd/sample_mpd_event_stream
similarity index 100%
rename from tree/library/dash/src/test/assets/sample_mpd_event_stream
rename to tree/testdata/src/test/assets/mpd/sample_mpd_event_stream
diff --git a/tree/library/dash/src/test/assets/sample_mpd_labels b/tree/testdata/src/test/assets/mpd/sample_mpd_labels
similarity index 100%
rename from tree/library/dash/src/test/assets/sample_mpd_labels
rename to tree/testdata/src/test/assets/mpd/sample_mpd_labels
diff --git a/tree/library/dash/src/test/assets/sample_mpd_segment_template b/tree/testdata/src/test/assets/mpd/sample_mpd_segment_template
similarity index 100%
rename from tree/library/dash/src/test/assets/sample_mpd_segment_template
rename to tree/testdata/src/test/assets/mpd/sample_mpd_segment_template
diff --git a/tree/testdata/src/test/assets/mpd/sample_mpd_text b/tree/testdata/src/test/assets/mpd/sample_mpd_text
new file mode 100644
index 0000000..d3235d5
--- /dev/null
+++ b/tree/testdata/src/test/assets/mpd/sample_mpd_text
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD type="static" duration="1s" mediaPresentationDuration="PT1S">
+ <Period>
+ <SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
+ <SegmentTimeline>
+ <S d="1000"/>
+ </SegmentTimeline>
+ </SegmentTemplate>
+ <AdaptationSet id="0" mimeType="application/x-rawcc" subsegmentAlignment="true">
+ <Representation id="0" codecs="cea608" bandwidth="16">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="0" mimeType="application/mp4" subsegmentAlignment="true">
+ <Representation id="0" codecs="stpp.ttml.im1t" bandwidth="16">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="0" mimeType="application/ttml+xml" subsegmentAlignment="true">
+ <Representation id="0" bandwidth="16">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/tree/library/dash/src/test/assets/sample_mpd_unknown_mime_type b/tree/testdata/src/test/assets/mpd/sample_mpd_unknown_mime_type
similarity index 100%
rename from tree/library/dash/src/test/assets/sample_mpd_unknown_mime_type
rename to tree/testdata/src/test/assets/mpd/sample_mpd_unknown_mime_type
diff --git a/tree/library/core/src/test/assets/offline/action_file_for_download_index_upgrade.exi b/tree/testdata/src/test/assets/offline/action_file_for_download_index_upgrade.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_for_download_index_upgrade.exi
rename to tree/testdata/src/test/assets/offline/action_file_for_download_index_upgrade.exi
Binary files differ
diff --git a/tree/library/core/src/test/assets/offline/action_file_incomplete_header.exi b/tree/testdata/src/test/assets/offline/action_file_incomplete_header.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_incomplete_header.exi
rename to tree/testdata/src/test/assets/offline/action_file_incomplete_header.exi
Binary files differ
diff --git a/tree/library/core/src/test/assets/offline/action_file_no_data.exi b/tree/testdata/src/test/assets/offline/action_file_no_data.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_no_data.exi
rename to tree/testdata/src/test/assets/offline/action_file_no_data.exi
diff --git a/tree/library/core/src/test/assets/offline/action_file_one_action.exi b/tree/testdata/src/test/assets/offline/action_file_one_action.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_one_action.exi
rename to tree/testdata/src/test/assets/offline/action_file_one_action.exi
Binary files differ
diff --git a/tree/library/core/src/test/assets/offline/action_file_two_actions.exi b/tree/testdata/src/test/assets/offline/action_file_two_actions.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_two_actions.exi
rename to tree/testdata/src/test/assets/offline/action_file_two_actions.exi
Binary files differ
diff --git a/tree/library/core/src/test/assets/offline/action_file_unsupported_version.exi b/tree/testdata/src/test/assets/offline/action_file_unsupported_version.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_unsupported_version.exi
rename to tree/testdata/src/test/assets/offline/action_file_unsupported_version.exi
Binary files differ
diff --git a/tree/library/core/src/test/assets/offline/action_file_zero_actions.exi b/tree/testdata/src/test/assets/offline/action_file_zero_actions.exi
similarity index 100%
rename from tree/library/core/src/test/assets/offline/action_file_zero_actions.exi
rename to tree/testdata/src/test/assets/offline/action_file_zero_actions.exi
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/bear.opus b/tree/testdata/src/test/assets/ogg/bear.opus
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/bear.opus
rename to tree/testdata/src/test/assets/ogg/bear.opus
Binary files differ
diff --git a/tree/testdata/src/test/assets/ogg/bear.opus.0.dump b/tree/testdata/src/test/assets/ogg/bear.opus.0.dump
new file mode 100644
index 0000000..2dccaa6
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear.opus.0.dump
@@ -0,0 +1,1120 @@
+seekMap:
+ isSeekable = true
+ duration = 2747500
+ getPosition(0) = [[timeUs=0, position=125]]
+ getPosition(1) = [[timeUs=1, position=125]]
+ getPosition(1373750) = [[timeUs=1373750, position=125]]
+ getPosition(2747500) = [[timeUs=2747500, position=125]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 25541
+ sample count = 275
+ format 0:
+ sampleMimeType = audio/opus
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 19, hash BFE794DB
+ data = length 8, hash CA22068C
+ data = length 8, hash 79C07075
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 234, hash B77BFFDA
+ sample 1:
+ time = 10000
+ flags = 1
+ data = length 165, hash F7B07C35
+ sample 2:
+ time = 20000
+ flags = 1
+ data = length 100, hash 21AFA81F
+ sample 3:
+ time = 30000
+ flags = 1
+ data = length 85, hash 9180DC2F
+ sample 4:
+ time = 40000
+ flags = 1
+ data = length 85, hash 6AE958C
+ sample 5:
+ time = 50000
+ flags = 1
+ data = length 86, hash C1C5AE60
+ sample 6:
+ time = 60000
+ flags = 1
+ data = length 87, hash B9BD2620
+ sample 7:
+ time = 70000
+ flags = 1
+ data = length 86, hash 5E69E6F9
+ sample 8:
+ time = 80000
+ flags = 1
+ data = length 90, hash C44C7DD9
+ sample 9:
+ time = 90000
+ flags = 1
+ data = length 86, hash C3FCDC6F
+ sample 10:
+ time = 100000
+ flags = 1
+ data = length 86, hash 44EA03BA
+ sample 11:
+ time = 110000
+ flags = 1
+ data = length 160, hash 9F4E1AE8
+ sample 12:
+ time = 120000
+ flags = 1
+ data = length 89, hash 31234460
+ sample 13:
+ time = 130000
+ flags = 1
+ data = length 91, hash 45012D79
+ sample 14:
+ time = 140000
+ flags = 1
+ data = length 90, hash B3E3AC75
+ sample 15:
+ time = 150000
+ flags = 1
+ data = length 87, hash B83B756B
+ sample 16:
+ time = 160000
+ flags = 1
+ data = length 86, hash 383921EB
+ sample 17:
+ time = 170000
+ flags = 1
+ data = length 97, hash 959AD270
+ sample 18:
+ time = 180000
+ flags = 1
+ data = length 92, hash 46C74FA8
+ sample 19:
+ time = 190000
+ flags = 1
+ data = length 91, hash CEA401DD
+ sample 20:
+ time = 200000
+ flags = 1
+ data = length 89, hash 48C41853
+ sample 21:
+ time = 210000
+ flags = 1
+ data = length 90, hash F23245BD
+ sample 22:
+ time = 220000
+ flags = 1
+ data = length 96, hash 95E8985D
+ sample 23:
+ time = 230000
+ flags = 1
+ data = length 96, hash 34295492
+ sample 24:
+ time = 240000
+ flags = 1
+ data = length 94, hash 4E3C9C0F
+ sample 25:
+ time = 250000
+ flags = 1
+ data = length 89, hash 28B74A29
+ sample 26:
+ time = 260000
+ flags = 1
+ data = length 87, hash BAC119A7
+ sample 27:
+ time = 270000
+ flags = 1
+ data = length 88, hash 7139FF38
+ sample 28:
+ time = 280000
+ flags = 1
+ data = length 85, hash 246E1D2A
+ sample 29:
+ time = 290000
+ flags = 1
+ data = length 86, hash 488A0900
+ sample 30:
+ time = 300000
+ flags = 1
+ data = length 90, hash 16FD17B1
+ sample 31:
+ time = 310000
+ flags = 1
+ data = length 87, hash 20E849D9
+ sample 32:
+ time = 320000
+ flags = 1
+ data = length 86, hash 23A0E9BA
+ sample 33:
+ time = 330000
+ flags = 1
+ data = length 87, hash EC935537
+ sample 34:
+ time = 340000
+ flags = 1
+ data = length 92, hash 4D9935AD
+ sample 35:
+ time = 350000
+ flags = 1
+ data = length 87, hash DEDE3FA
+ sample 36:
+ time = 360000
+ flags = 1
+ data = length 87, hash ADC25A6E
+ sample 37:
+ time = 370000
+ flags = 1
+ data = length 88, hash A1C828C5
+ sample 38:
+ time = 380000
+ flags = 1
+ data = length 89, hash 735C087A
+ sample 39:
+ time = 390000
+ flags = 1
+ data = length 89, hash 19AF5D10
+ sample 40:
+ time = 400000
+ flags = 1
+ data = length 90, hash BCCEA2BB
+ sample 41:
+ time = 410000
+ flags = 1
+ data = length 86, hash A7C934A0
+ sample 42:
+ time = 420000
+ flags = 1
+ data = length 86, hash 28BBC0A8
+ sample 43:
+ time = 430000
+ flags = 1
+ data = length 85, hash E60BB12D
+ sample 44:
+ time = 440000
+ flags = 1
+ data = length 141, hash 1D2B8920
+ sample 45:
+ time = 450000
+ flags = 1
+ data = length 121, hash 8AA3E19C
+ sample 46:
+ time = 460000
+ flags = 1
+ data = length 86, hash 24DF0F37
+ sample 47:
+ time = 470000
+ flags = 1
+ data = length 86, hash 1D1775FF
+ sample 48:
+ time = 480000
+ flags = 1
+ data = length 87, hash 5230399E
+ sample 49:
+ time = 490000
+ flags = 1
+ data = length 91, hash 6CD98305
+ sample 50:
+ time = 500000
+ flags = 1
+ data = length 88, hash 4069FBB
+ sample 51:
+ time = 510000
+ flags = 1
+ data = length 89, hash 76824ABF
+ sample 52:
+ time = 520000
+ flags = 1
+ data = length 87, hash BC1B1322
+ sample 53:
+ time = 530000
+ flags = 1
+ data = length 102, hash E01BA053
+ sample 54:
+ time = 540000
+ flags = 1
+ data = length 85, hash C09B626D
+ sample 55:
+ time = 550000
+ flags = 1
+ data = length 88, hash 6B7B404A
+ sample 56:
+ time = 560000
+ flags = 1
+ data = length 85, hash 74A15DC7
+ sample 57:
+ time = 570000
+ flags = 1
+ data = length 88, hash 38DB82E5
+ sample 58:
+ time = 580000
+ flags = 1
+ data = length 86, hash 1A39C081
+ sample 59:
+ time = 590000
+ flags = 1
+ data = length 87, hash 39FEC92
+ sample 60:
+ time = 600000
+ flags = 1
+ data = length 92, hash 278EA09
+ sample 61:
+ time = 610000
+ flags = 1
+ data = length 87, hash 28265F2D
+ sample 62:
+ time = 620000
+ flags = 1
+ data = length 86, hash CC2040C6
+ sample 63:
+ time = 630000
+ flags = 1
+ data = length 138, hash 9E07BC1F
+ sample 64:
+ time = 640000
+ flags = 1
+ data = length 85, hash 4F299670
+ sample 65:
+ time = 650000
+ flags = 1
+ data = length 125, hash B61123C3
+ sample 66:
+ time = 660000
+ flags = 1
+ data = length 89, hash 5CC688ED
+ sample 67:
+ time = 670000
+ flags = 1
+ data = length 88, hash 84AF64A6
+ sample 68:
+ time = 680000
+ flags = 1
+ data = length 89, hash A9BFC8DC
+ sample 69:
+ time = 690000
+ flags = 1
+ data = length 90, hash 2FF77060
+ sample 70:
+ time = 700000
+ flags = 1
+ data = length 96, hash E11AFD61
+ sample 71:
+ time = 710000
+ flags = 1
+ data = length 87, hash 85D14EDA
+ sample 72:
+ time = 720000
+ flags = 1
+ data = length 88, hash 5FC71D53
+ sample 73:
+ time = 730000
+ flags = 1
+ data = length 89, hash 957187B6
+ sample 74:
+ time = 740000
+ flags = 1
+ data = length 89, hash 5A776082
+ sample 75:
+ time = 750000
+ flags = 1
+ data = length 87, hash E8A83AFE
+ sample 76:
+ time = 760000
+ flags = 1
+ data = length 87, hash F1989133
+ sample 77:
+ time = 770000
+ flags = 1
+ data = length 87, hash FA06BCCA
+ sample 78:
+ time = 780000
+ flags = 1
+ data = length 86, hash BD97E0C0
+ sample 79:
+ time = 790000
+ flags = 1
+ data = length 88, hash E6AE022C
+ sample 80:
+ time = 800000
+ flags = 1
+ data = length 87, hash FB6C6169
+ sample 81:
+ time = 810000
+ flags = 1
+ data = length 87, hash DFFCD2CF
+ sample 82:
+ time = 820000
+ flags = 1
+ data = length 88, hash A4B5EB52
+ sample 83:
+ time = 830000
+ flags = 1
+ data = length 85, hash A63CF4EA
+ sample 84:
+ time = 840000
+ flags = 1
+ data = length 86, hash F126E7C7
+ sample 85:
+ time = 850000
+ flags = 1
+ data = length 86, hash 21A8B22F
+ sample 86:
+ time = 860000
+ flags = 1
+ data = length 87, hash 6520E7C1
+ sample 87:
+ time = 870000
+ flags = 1
+ data = length 88, hash 825B7423
+ sample 88:
+ time = 880000
+ flags = 1
+ data = length 88, hash DF3DBD48
+ sample 89:
+ time = 890000
+ flags = 1
+ data = length 87, hash B32C68D0
+ sample 90:
+ time = 900000
+ flags = 1
+ data = length 89, hash B99DFFCA
+ sample 91:
+ time = 910000
+ flags = 1
+ data = length 88, hash 9C8D5178
+ sample 92:
+ time = 920000
+ flags = 1
+ data = length 88, hash 48A0B19A
+ sample 93:
+ time = 930000
+ flags = 1
+ data = length 88, hash B62C94DD
+ sample 94:
+ time = 940000
+ flags = 1
+ data = length 92, hash 96DBDD46
+ sample 95:
+ time = 950000
+ flags = 1
+ data = length 87, hash 7B80E6F
+ sample 96:
+ time = 960000
+ flags = 1
+ data = length 86, hash 9C60225B
+ sample 97:
+ time = 970000
+ flags = 1
+ data = length 87, hash 45F7E6E8
+ sample 98:
+ time = 980000
+ flags = 1
+ data = length 87, hash DDC2D592
+ sample 99:
+ time = 990000
+ flags = 1
+ data = length 91, hash 173D3B26
+ sample 100:
+ time = 1000000
+ flags = 1
+ data = length 87, hash CF3629DF
+ sample 101:
+ time = 1010000
+ flags = 1
+ data = length 87, hash BBE2E7B3
+ sample 102:
+ time = 1020000
+ flags = 1
+ data = length 89, hash 89AFFB10
+ sample 103:
+ time = 1030000
+ flags = 1
+ data = length 88, hash 510DCC90
+ sample 104:
+ time = 1040000
+ flags = 1
+ data = length 88, hash CBA56E5F
+ sample 105:
+ time = 1050000
+ flags = 1
+ data = length 87, hash B4B1B3FF
+ sample 106:
+ time = 1060000
+ flags = 1
+ data = length 89, hash B976A537
+ sample 107:
+ time = 1070000
+ flags = 1
+ data = length 96, hash 43ECF2C1
+ sample 108:
+ time = 1080000
+ flags = 1
+ data = length 90, hash BB7ECB44
+ sample 109:
+ time = 1090000
+ flags = 1
+ data = length 89, hash B8E221A5
+ sample 110:
+ time = 1100000
+ flags = 1
+ data = length 86, hash B35BEF5B
+ sample 111:
+ time = 1110000
+ flags = 1
+ data = length 89, hash 9002F0EC
+ sample 112:
+ time = 1120000
+ flags = 1
+ data = length 85, hash F694B20
+ sample 113:
+ time = 1130000
+ flags = 1
+ data = length 87, hash D7CC386E
+ sample 114:
+ time = 1140000
+ flags = 1
+ data = length 89, hash EE9E0E79
+ sample 115:
+ time = 1150000
+ flags = 1
+ data = length 90, hash CA72C96B
+ sample 116:
+ time = 1160000
+ flags = 1
+ data = length 112, hash 4AD3D1B1
+ sample 117:
+ time = 1170000
+ flags = 1
+ data = length 87, hash FA568FAB
+ sample 118:
+ time = 1180000
+ flags = 1
+ data = length 90, hash 6E6948D2
+ sample 119:
+ time = 1190000
+ flags = 1
+ data = length 89, hash 5465A762
+ sample 120:
+ time = 1200000
+ flags = 1
+ data = length 87, hash BED19B40
+ sample 121:
+ time = 1210000
+ flags = 1
+ data = length 89, hash 5D05836A
+ sample 122:
+ time = 1220000
+ flags = 1
+ data = length 87, hash A8A3EF5A
+ sample 123:
+ time = 1230000
+ flags = 1
+ data = length 90, hash 6425A77A
+ sample 124:
+ time = 1240000
+ flags = 1
+ data = length 92, hash 7F320FA
+ sample 125:
+ time = 1250000
+ flags = 1
+ data = length 89, hash 2C7837D6
+ sample 126:
+ time = 1260000
+ flags = 1
+ data = length 86, hash 58D56685
+ sample 127:
+ time = 1270000
+ flags = 1
+ data = length 91, hash ADC5072F
+ sample 128:
+ time = 1280000
+ flags = 1
+ data = length 85, hash 53EFD93
+ sample 129:
+ time = 1290000
+ flags = 1
+ data = length 87, hash D006B535
+ sample 130:
+ time = 1300000
+ flags = 1
+ data = length 86, hash AE944625
+ sample 131:
+ time = 1310000
+ flags = 1
+ data = length 89, hash B5D3C81D
+ sample 132:
+ time = 1320000
+ flags = 1
+ data = length 86, hash 3BB1D0E7
+ sample 133:
+ time = 1330000
+ flags = 1
+ data = length 102, hash 16EEC441
+ sample 134:
+ time = 1340000
+ flags = 1
+ data = length 90, hash 1005B936
+ sample 135:
+ time = 1350000
+ flags = 1
+ data = length 85, hash 15EEBF9A
+ sample 136:
+ time = 1360000
+ flags = 1
+ data = length 87, hash 37C83AC2
+ sample 137:
+ time = 1370000
+ flags = 1
+ data = length 85, hash 2D27855D
+ sample 138:
+ time = 1380000
+ flags = 1
+ data = length 85, hash 753EB7C6
+ sample 139:
+ time = 1390000
+ flags = 1
+ data = length 91, hash C0813318
+ sample 140:
+ time = 1400000
+ flags = 1
+ data = length 89, hash 3A6468AC
+ sample 141:
+ time = 1410000
+ flags = 1
+ data = length 88, hash 3D220ABC
+ sample 142:
+ time = 1420000
+ flags = 1
+ data = length 140, hash 7949ABC7
+ sample 143:
+ time = 1430000
+ flags = 1
+ data = length 92, hash F19AFA45
+ sample 144:
+ time = 1440000
+ flags = 1
+ data = length 90, hash 3D21587C
+ sample 145:
+ time = 1450000
+ flags = 1
+ data = length 89, hash 5C12226C
+ sample 146:
+ time = 1460000
+ flags = 1
+ data = length 90, hash 22BA14FC
+ sample 147:
+ time = 1470000
+ flags = 1
+ data = length 88, hash F064B21C
+ sample 148:
+ time = 1480000
+ flags = 1
+ data = length 87, hash 6D7906B9
+ sample 149:
+ time = 1490000
+ flags = 1
+ data = length 88, hash 6756A484
+ sample 150:
+ time = 1500000
+ flags = 1
+ data = length 91, hash C95C00B6
+ sample 151:
+ time = 1510000
+ flags = 1
+ data = length 87, hash 728D8119
+ sample 152:
+ time = 1520000
+ flags = 1
+ data = length 90, hash C43DA1B4
+ sample 153:
+ time = 1530000
+ flags = 1
+ data = length 88, hash C181BB57
+ sample 154:
+ time = 1540000
+ flags = 1
+ data = length 84, hash F75B1639
+ sample 155:
+ time = 1550000
+ flags = 1
+ data = length 87, hash B6F32978
+ sample 156:
+ time = 1560000
+ flags = 1
+ data = length 90, hash 36D6E2D7
+ sample 157:
+ time = 1570000
+ flags = 1
+ data = length 87, hash 4C9657A7
+ sample 158:
+ time = 1580000
+ flags = 1
+ data = length 89, hash C3BDB9B7
+ sample 159:
+ time = 1590000
+ flags = 1
+ data = length 88, hash DB51087E
+ sample 160:
+ time = 1600000
+ flags = 1
+ data = length 86, hash 1550F998
+ sample 161:
+ time = 1610000
+ flags = 1
+ data = length 86, hash A445FAD4
+ sample 162:
+ time = 1620000
+ flags = 1
+ data = length 85, hash 60D3362C
+ sample 163:
+ time = 1630000
+ flags = 1
+ data = length 172, hash 945D63E4
+ sample 164:
+ time = 1640000
+ flags = 1
+ data = length 107, hash 585B7C04
+ sample 165:
+ time = 1650000
+ flags = 1
+ data = length 110, hash 74BECF69
+ sample 166:
+ time = 1660000
+ flags = 1
+ data = length 87, hash 63DE1D24
+ sample 167:
+ time = 1670000
+ flags = 1
+ data = length 90, hash 1C1D28DB
+ sample 168:
+ time = 1680000
+ flags = 1
+ data = length 87, hash CB382A67
+ sample 169:
+ time = 1690000
+ flags = 1
+ data = length 85, hash A227BA13
+ sample 170:
+ time = 1700000
+ flags = 1
+ data = length 86, hash EFD8B10B
+ sample 171:
+ time = 1710000
+ flags = 1
+ data = length 87, hash 47FF364A
+ sample 172:
+ time = 1720000
+ flags = 1
+ data = length 91, hash 31D4B48A
+ sample 173:
+ time = 1730000
+ flags = 1
+ data = length 91, hash DD69BD85
+ sample 174:
+ time = 1740000
+ flags = 1
+ data = length 88, hash AF1A95C6
+ sample 175:
+ time = 1750000
+ flags = 1
+ data = length 87, hash 2FB8AF74
+ sample 176:
+ time = 1760000
+ flags = 1
+ data = length 92, hash 173C707A
+ sample 177:
+ time = 1770000
+ flags = 1
+ data = length 88, hash 5F58F5E8
+ sample 178:
+ time = 1780000
+ flags = 1
+ data = length 91, hash D449785F
+ sample 179:
+ time = 1790000
+ flags = 1
+ data = length 91, hash CE2CB465
+ sample 180:
+ time = 1800000
+ flags = 1
+ data = length 93, hash ABC1C62E
+ sample 181:
+ time = 1810000
+ flags = 1
+ data = length 87, hash 83B4B9A0
+ sample 182:
+ time = 1820000
+ flags = 1
+ data = length 87, hash 3220D562
+ sample 183:
+ time = 1830000
+ flags = 1
+ data = length 86, hash 64D21AA1
+ sample 184:
+ time = 1840000
+ flags = 1
+ data = length 86, hash A1FAAF2C
+ sample 185:
+ time = 1850000
+ flags = 1
+ data = length 86, hash ECA80F7E
+ sample 186:
+ time = 1860000
+ flags = 1
+ data = length 86, hash FEB03B2C
+ sample 187:
+ time = 1870000
+ flags = 1
+ data = length 85, hash 2C2E6B2F
+ sample 188:
+ time = 1880000
+ flags = 1
+ data = length 89, hash A0D7AC3
+ sample 189:
+ time = 1890000
+ flags = 1
+ data = length 87, hash 83739547
+ sample 190:
+ time = 1900000
+ flags = 1
+ data = length 86, hash 991E531E
+ sample 191:
+ time = 1910000
+ flags = 1
+ data = length 88, hash 16B287A3
+ sample 192:
+ time = 1920000
+ flags = 1
+ data = length 86, hash FC86EED
+ sample 193:
+ time = 1930000
+ flags = 1
+ data = length 86, hash 96AF0248
+ sample 194:
+ time = 1940000
+ flags = 1
+ data = length 86, hash 288402C8
+ sample 195:
+ time = 1950000
+ flags = 1
+ data = length 87, hash 4BBA7912
+ sample 196:
+ time = 1960000
+ flags = 1
+ data = length 86, hash 4A59C719
+ sample 197:
+ time = 1970000
+ flags = 1
+ data = length 85, hash 906E8187
+ sample 198:
+ time = 1980000
+ flags = 1
+ data = length 90, hash CB8B755D
+ sample 199:
+ time = 1990000
+ flags = 1
+ data = length 87, hash C8E02C
+ sample 200:
+ time = 2000000
+ flags = 1
+ data = length 88, hash ACF4D89A
+ sample 201:
+ time = 2010000
+ flags = 1
+ data = length 86, hash 510FE048
+ sample 202:
+ time = 2020000
+ flags = 1
+ data = length 86, hash 64983E46
+ sample 203:
+ time = 2030000
+ flags = 1
+ data = length 86, hash CEA76A1E
+ sample 204:
+ time = 2040000
+ flags = 1
+ data = length 87, hash AADE498E
+ sample 205:
+ time = 2050000
+ flags = 1
+ data = length 127, hash 353A6D8C
+ sample 206:
+ time = 2060000
+ flags = 1
+ data = length 87, hash 29E18E62
+ sample 207:
+ time = 2070000
+ flags = 1
+ data = length 87, hash 2CF7B30F
+ sample 208:
+ time = 2080000
+ flags = 1
+ data = length 94, hash 758704C3
+ sample 209:
+ time = 2090000
+ flags = 1
+ data = length 88, hash C2153A4C
+ sample 210:
+ time = 2100000
+ flags = 1
+ data = length 86, hash A0A83DA5
+ sample 211:
+ time = 2110000
+ flags = 1
+ data = length 86, hash 41017D7F
+ sample 212:
+ time = 2120000
+ flags = 1
+ data = length 93, hash 686B0CA2
+ sample 213:
+ time = 2130000
+ flags = 1
+ data = length 86, hash 554D16CC
+ sample 214:
+ time = 2140000
+ flags = 1
+ data = length 88, hash 99D72771
+ sample 215:
+ time = 2150000
+ flags = 1
+ data = length 88, hash 7176DFBF
+ sample 216:
+ time = 2160000
+ flags = 1
+ data = length 86, hash BAA22669
+ sample 217:
+ time = 2170000
+ flags = 1
+ data = length 88, hash B00B0D3C
+ sample 218:
+ time = 2180000
+ flags = 1
+ data = length 89, hash 73FED83A
+ sample 219:
+ time = 2190000
+ flags = 1
+ data = length 86, hash 4A4138D3
+ sample 220:
+ time = 2200000
+ flags = 1
+ data = length 89, hash E0A860FF
+ sample 221:
+ time = 2210000
+ flags = 1
+ data = length 95, hash EE5A8AED
+ sample 222:
+ time = 2220000
+ flags = 1
+ data = length 92, hash 36DBD7FD
+ sample 223:
+ time = 2230000
+ flags = 1
+ data = length 88, hash EE47A7E4
+ sample 224:
+ time = 2240000
+ flags = 1
+ data = length 100, hash 2E1A603F
+ sample 225:
+ time = 2250000
+ flags = 1
+ data = length 89, hash 657ED4A3
+ sample 226:
+ time = 2260000
+ flags = 1
+ data = length 86, hash A833DC7B
+ sample 227:
+ time = 2270000
+ flags = 1
+ data = length 88, hash 81E80732
+ sample 228:
+ time = 2280000
+ flags = 1
+ data = length 91, hash FA256A0F
+ sample 229:
+ time = 2290000
+ flags = 1
+ data = length 88, hash A63A4DBA
+ sample 230:
+ time = 2300000
+ flags = 1
+ data = length 88, hash 67910A9F
+ sample 231:
+ time = 2310000
+ flags = 1
+ data = length 86, hash EB387DB6
+ sample 232:
+ time = 2320000
+ flags = 1
+ data = length 88, hash 5ACAAC2A
+ sample 233:
+ time = 2330000
+ flags = 1
+ data = length 86, hash 6ADF2E1F
+ sample 234:
+ time = 2340000
+ flags = 1
+ data = length 85, hash 9D064471
+ sample 235:
+ time = 2350000
+ flags = 1
+ data = length 87, hash F176C59
+ sample 236:
+ time = 2360000
+ flags = 1
+ data = length 89, hash 5CA40CE4
+ sample 237:
+ time = 2370000
+ flags = 1
+ data = length 88, hash 67B944FC
+ sample 238:
+ time = 2380000
+ flags = 1
+ data = length 86, hash B3A84EC8
+ sample 239:
+ time = 2390000
+ flags = 1
+ data = length 92, hash A6ACF94B
+ sample 240:
+ time = 2400000
+ flags = 1
+ data = length 88, hash CB0C9730
+ sample 241:
+ time = 2410000
+ flags = 1
+ data = length 88, hash C79FE804
+ sample 242:
+ time = 2420000
+ flags = 1
+ data = length 88, hash A74C7F0A
+ sample 243:
+ time = 2430000
+ flags = 1
+ data = length 91, hash 55F6F0A5
+ sample 244:
+ time = 2440000
+ flags = 1
+ data = length 93, hash 330F33E7
+ sample 245:
+ time = 2450000
+ flags = 1
+ data = length 89, hash 614AFBA0
+ sample 246:
+ time = 2460000
+ flags = 1
+ data = length 87, hash 3CE4652D
+ sample 247:
+ time = 2470000
+ flags = 1
+ data = length 87, hash 4EFD5467
+ sample 248:
+ time = 2480000
+ flags = 1
+ data = length 86, hash D81B3EB8
+ sample 249:
+ time = 2490000
+ flags = 1
+ data = length 88, hash 96CB6871
+ sample 250:
+ time = 2500000
+ flags = 1
+ data = length 88, hash E9DF2786
+ sample 251:
+ time = 2510000
+ flags = 1
+ data = length 89, hash 2CA33D96
+ sample 252:
+ time = 2520000
+ flags = 1
+ data = length 90, hash 96BDE594
+ sample 253:
+ time = 2530000
+ flags = 1
+ data = length 87, hash C261493C
+ sample 254:
+ time = 2540000
+ flags = 1
+ data = length 86, hash D037318E
+ sample 255:
+ time = 2550000
+ flags = 1
+ data = length 88, hash BC15BC88
+ sample 256:
+ time = 2560000
+ flags = 1
+ data = length 91, hash A8361A51
+ sample 257:
+ time = 2570000
+ flags = 1
+ data = length 87, hash 4AFDB5F2
+ sample 258:
+ time = 2580000
+ flags = 1
+ data = length 87, hash 6447F8CB
+ sample 259:
+ time = 2590000
+ flags = 1
+ data = length 89, hash 48305229
+ sample 260:
+ time = 2600000
+ flags = 1
+ data = length 87, hash 8741D9E7
+ sample 261:
+ time = 2610000
+ flags = 1
+ data = length 120, hash 761F020C
+ sample 262:
+ time = 2620000
+ flags = 1
+ data = length 139, hash AECE2E57
+ sample 263:
+ time = 2630000
+ flags = 1
+ data = length 166, hash 6288797A
+ sample 264:
+ time = 2640000
+ flags = 1
+ data = length 144, hash 437821A0
+ sample 265:
+ time = 2650000
+ flags = 1
+ data = length 113, hash FCCBEDF1
+ sample 266:
+ time = 2660000
+ flags = 1
+ data = length 108, hash C4040614
+ sample 267:
+ time = 2670000
+ flags = 1
+ data = length 125, hash E29064C2
+ sample 268:
+ time = 2680000
+ flags = 1
+ data = length 126, hash D42D24FF
+ sample 269:
+ time = 2690000
+ flags = 1
+ data = length 122, hash 30AF267D
+ sample 270:
+ time = 2700000
+ flags = 1
+ data = length 122, hash 45CEC1FB
+ sample 271:
+ time = 2710000
+ flags = 1
+ data = length 134, hash 59143FE2
+ sample 272:
+ time = 2720000
+ flags = 1
+ data = length 134, hash BD52A84
+ sample 273:
+ time = 2730000
+ flags = 1
+ data = length 120, hash 745C3714
+ sample 274:
+ time = 2740000
+ flags = 1
+ data = length 126, hash 505E117B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear.opus.1.dump b/tree/testdata/src/test/assets/ogg/bear.opus.1.dump
new file mode 100644
index 0000000..c5786e3
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear.opus.1.dump
@@ -0,0 +1,756 @@
+seekMap:
+ isSeekable = true
+ duration = 2747500
+ getPosition(0) = [[timeUs=0, position=125]]
+ getPosition(1) = [[timeUs=1, position=125]]
+ getPosition(1373750) = [[timeUs=1373750, position=125]]
+ getPosition(2747500) = [[timeUs=2747500, position=125]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 17031
+ sample count = 184
+ format 0:
+ sampleMimeType = audio/opus
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 19, hash BFE794DB
+ data = length 8, hash CA22068C
+ data = length 8, hash 79C07075
+ sample 0:
+ time = 910000
+ flags = 1
+ data = length 88, hash 9C8D5178
+ sample 1:
+ time = 920000
+ flags = 1
+ data = length 88, hash 48A0B19A
+ sample 2:
+ time = 930000
+ flags = 1
+ data = length 88, hash B62C94DD
+ sample 3:
+ time = 940000
+ flags = 1
+ data = length 92, hash 96DBDD46
+ sample 4:
+ time = 950000
+ flags = 1
+ data = length 87, hash 7B80E6F
+ sample 5:
+ time = 960000
+ flags = 1
+ data = length 86, hash 9C60225B
+ sample 6:
+ time = 970000
+ flags = 1
+ data = length 87, hash 45F7E6E8
+ sample 7:
+ time = 980000
+ flags = 1
+ data = length 87, hash DDC2D592
+ sample 8:
+ time = 990000
+ flags = 1
+ data = length 91, hash 173D3B26
+ sample 9:
+ time = 1000000
+ flags = 1
+ data = length 87, hash CF3629DF
+ sample 10:
+ time = 1010000
+ flags = 1
+ data = length 87, hash BBE2E7B3
+ sample 11:
+ time = 1020000
+ flags = 1
+ data = length 89, hash 89AFFB10
+ sample 12:
+ time = 1030000
+ flags = 1
+ data = length 88, hash 510DCC90
+ sample 13:
+ time = 1040000
+ flags = 1
+ data = length 88, hash CBA56E5F
+ sample 14:
+ time = 1050000
+ flags = 1
+ data = length 87, hash B4B1B3FF
+ sample 15:
+ time = 1060000
+ flags = 1
+ data = length 89, hash B976A537
+ sample 16:
+ time = 1070000
+ flags = 1
+ data = length 96, hash 43ECF2C1
+ sample 17:
+ time = 1080000
+ flags = 1
+ data = length 90, hash BB7ECB44
+ sample 18:
+ time = 1090000
+ flags = 1
+ data = length 89, hash B8E221A5
+ sample 19:
+ time = 1100000
+ flags = 1
+ data = length 86, hash B35BEF5B
+ sample 20:
+ time = 1110000
+ flags = 1
+ data = length 89, hash 9002F0EC
+ sample 21:
+ time = 1120000
+ flags = 1
+ data = length 85, hash F694B20
+ sample 22:
+ time = 1130000
+ flags = 1
+ data = length 87, hash D7CC386E
+ sample 23:
+ time = 1140000
+ flags = 1
+ data = length 89, hash EE9E0E79
+ sample 24:
+ time = 1150000
+ flags = 1
+ data = length 90, hash CA72C96B
+ sample 25:
+ time = 1160000
+ flags = 1
+ data = length 112, hash 4AD3D1B1
+ sample 26:
+ time = 1170000
+ flags = 1
+ data = length 87, hash FA568FAB
+ sample 27:
+ time = 1180000
+ flags = 1
+ data = length 90, hash 6E6948D2
+ sample 28:
+ time = 1190000
+ flags = 1
+ data = length 89, hash 5465A762
+ sample 29:
+ time = 1200000
+ flags = 1
+ data = length 87, hash BED19B40
+ sample 30:
+ time = 1210000
+ flags = 1
+ data = length 89, hash 5D05836A
+ sample 31:
+ time = 1220000
+ flags = 1
+ data = length 87, hash A8A3EF5A
+ sample 32:
+ time = 1230000
+ flags = 1
+ data = length 90, hash 6425A77A
+ sample 33:
+ time = 1240000
+ flags = 1
+ data = length 92, hash 7F320FA
+ sample 34:
+ time = 1250000
+ flags = 1
+ data = length 89, hash 2C7837D6
+ sample 35:
+ time = 1260000
+ flags = 1
+ data = length 86, hash 58D56685
+ sample 36:
+ time = 1270000
+ flags = 1
+ data = length 91, hash ADC5072F
+ sample 37:
+ time = 1280000
+ flags = 1
+ data = length 85, hash 53EFD93
+ sample 38:
+ time = 1290000
+ flags = 1
+ data = length 87, hash D006B535
+ sample 39:
+ time = 1300000
+ flags = 1
+ data = length 86, hash AE944625
+ sample 40:
+ time = 1310000
+ flags = 1
+ data = length 89, hash B5D3C81D
+ sample 41:
+ time = 1320000
+ flags = 1
+ data = length 86, hash 3BB1D0E7
+ sample 42:
+ time = 1330000
+ flags = 1
+ data = length 102, hash 16EEC441
+ sample 43:
+ time = 1340000
+ flags = 1
+ data = length 90, hash 1005B936
+ sample 44:
+ time = 1350000
+ flags = 1
+ data = length 85, hash 15EEBF9A
+ sample 45:
+ time = 1360000
+ flags = 1
+ data = length 87, hash 37C83AC2
+ sample 46:
+ time = 1370000
+ flags = 1
+ data = length 85, hash 2D27855D
+ sample 47:
+ time = 1380000
+ flags = 1
+ data = length 85, hash 753EB7C6
+ sample 48:
+ time = 1390000
+ flags = 1
+ data = length 91, hash C0813318
+ sample 49:
+ time = 1400000
+ flags = 1
+ data = length 89, hash 3A6468AC
+ sample 50:
+ time = 1410000
+ flags = 1
+ data = length 88, hash 3D220ABC
+ sample 51:
+ time = 1420000
+ flags = 1
+ data = length 140, hash 7949ABC7
+ sample 52:
+ time = 1430000
+ flags = 1
+ data = length 92, hash F19AFA45
+ sample 53:
+ time = 1440000
+ flags = 1
+ data = length 90, hash 3D21587C
+ sample 54:
+ time = 1450000
+ flags = 1
+ data = length 89, hash 5C12226C
+ sample 55:
+ time = 1460000
+ flags = 1
+ data = length 90, hash 22BA14FC
+ sample 56:
+ time = 1470000
+ flags = 1
+ data = length 88, hash F064B21C
+ sample 57:
+ time = 1480000
+ flags = 1
+ data = length 87, hash 6D7906B9
+ sample 58:
+ time = 1490000
+ flags = 1
+ data = length 88, hash 6756A484
+ sample 59:
+ time = 1500000
+ flags = 1
+ data = length 91, hash C95C00B6
+ sample 60:
+ time = 1510000
+ flags = 1
+ data = length 87, hash 728D8119
+ sample 61:
+ time = 1520000
+ flags = 1
+ data = length 90, hash C43DA1B4
+ sample 62:
+ time = 1530000
+ flags = 1
+ data = length 88, hash C181BB57
+ sample 63:
+ time = 1540000
+ flags = 1
+ data = length 84, hash F75B1639
+ sample 64:
+ time = 1550000
+ flags = 1
+ data = length 87, hash B6F32978
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 90, hash 36D6E2D7
+ sample 66:
+ time = 1570000
+ flags = 1
+ data = length 87, hash 4C9657A7
+ sample 67:
+ time = 1580000
+ flags = 1
+ data = length 89, hash C3BDB9B7
+ sample 68:
+ time = 1590000
+ flags = 1
+ data = length 88, hash DB51087E
+ sample 69:
+ time = 1600000
+ flags = 1
+ data = length 86, hash 1550F998
+ sample 70:
+ time = 1610000
+ flags = 1
+ data = length 86, hash A445FAD4
+ sample 71:
+ time = 1620000
+ flags = 1
+ data = length 85, hash 60D3362C
+ sample 72:
+ time = 1630000
+ flags = 1
+ data = length 172, hash 945D63E4
+ sample 73:
+ time = 1640000
+ flags = 1
+ data = length 107, hash 585B7C04
+ sample 74:
+ time = 1650000
+ flags = 1
+ data = length 110, hash 74BECF69
+ sample 75:
+ time = 1660000
+ flags = 1
+ data = length 87, hash 63DE1D24
+ sample 76:
+ time = 1670000
+ flags = 1
+ data = length 90, hash 1C1D28DB
+ sample 77:
+ time = 1680000
+ flags = 1
+ data = length 87, hash CB382A67
+ sample 78:
+ time = 1690000
+ flags = 1
+ data = length 85, hash A227BA13
+ sample 79:
+ time = 1700000
+ flags = 1
+ data = length 86, hash EFD8B10B
+ sample 80:
+ time = 1710000
+ flags = 1
+ data = length 87, hash 47FF364A
+ sample 81:
+ time = 1720000
+ flags = 1
+ data = length 91, hash 31D4B48A
+ sample 82:
+ time = 1730000
+ flags = 1
+ data = length 91, hash DD69BD85
+ sample 83:
+ time = 1740000
+ flags = 1
+ data = length 88, hash AF1A95C6
+ sample 84:
+ time = 1750000
+ flags = 1
+ data = length 87, hash 2FB8AF74
+ sample 85:
+ time = 1760000
+ flags = 1
+ data = length 92, hash 173C707A
+ sample 86:
+ time = 1770000
+ flags = 1
+ data = length 88, hash 5F58F5E8
+ sample 87:
+ time = 1780000
+ flags = 1
+ data = length 91, hash D449785F
+ sample 88:
+ time = 1790000
+ flags = 1
+ data = length 91, hash CE2CB465
+ sample 89:
+ time = 1800000
+ flags = 1
+ data = length 93, hash ABC1C62E
+ sample 90:
+ time = 1810000
+ flags = 1
+ data = length 87, hash 83B4B9A0
+ sample 91:
+ time = 1820000
+ flags = 1
+ data = length 87, hash 3220D562
+ sample 92:
+ time = 1830000
+ flags = 1
+ data = length 86, hash 64D21AA1
+ sample 93:
+ time = 1840000
+ flags = 1
+ data = length 86, hash A1FAAF2C
+ sample 94:
+ time = 1850000
+ flags = 1
+ data = length 86, hash ECA80F7E
+ sample 95:
+ time = 1860000
+ flags = 1
+ data = length 86, hash FEB03B2C
+ sample 96:
+ time = 1870000
+ flags = 1
+ data = length 85, hash 2C2E6B2F
+ sample 97:
+ time = 1880000
+ flags = 1
+ data = length 89, hash A0D7AC3
+ sample 98:
+ time = 1890000
+ flags = 1
+ data = length 87, hash 83739547
+ sample 99:
+ time = 1900000
+ flags = 1
+ data = length 86, hash 991E531E
+ sample 100:
+ time = 1910000
+ flags = 1
+ data = length 88, hash 16B287A3
+ sample 101:
+ time = 1920000
+ flags = 1
+ data = length 86, hash FC86EED
+ sample 102:
+ time = 1930000
+ flags = 1
+ data = length 86, hash 96AF0248
+ sample 103:
+ time = 1940000
+ flags = 1
+ data = length 86, hash 288402C8
+ sample 104:
+ time = 1950000
+ flags = 1
+ data = length 87, hash 4BBA7912
+ sample 105:
+ time = 1960000
+ flags = 1
+ data = length 86, hash 4A59C719
+ sample 106:
+ time = 1970000
+ flags = 1
+ data = length 85, hash 906E8187
+ sample 107:
+ time = 1980000
+ flags = 1
+ data = length 90, hash CB8B755D
+ sample 108:
+ time = 1990000
+ flags = 1
+ data = length 87, hash C8E02C
+ sample 109:
+ time = 2000000
+ flags = 1
+ data = length 88, hash ACF4D89A
+ sample 110:
+ time = 2010000
+ flags = 1
+ data = length 86, hash 510FE048
+ sample 111:
+ time = 2020000
+ flags = 1
+ data = length 86, hash 64983E46
+ sample 112:
+ time = 2030000
+ flags = 1
+ data = length 86, hash CEA76A1E
+ sample 113:
+ time = 2040000
+ flags = 1
+ data = length 87, hash AADE498E
+ sample 114:
+ time = 2050000
+ flags = 1
+ data = length 127, hash 353A6D8C
+ sample 115:
+ time = 2060000
+ flags = 1
+ data = length 87, hash 29E18E62
+ sample 116:
+ time = 2070000
+ flags = 1
+ data = length 87, hash 2CF7B30F
+ sample 117:
+ time = 2080000
+ flags = 1
+ data = length 94, hash 758704C3
+ sample 118:
+ time = 2090000
+ flags = 1
+ data = length 88, hash C2153A4C
+ sample 119:
+ time = 2100000
+ flags = 1
+ data = length 86, hash A0A83DA5
+ sample 120:
+ time = 2110000
+ flags = 1
+ data = length 86, hash 41017D7F
+ sample 121:
+ time = 2120000
+ flags = 1
+ data = length 93, hash 686B0CA2
+ sample 122:
+ time = 2130000
+ flags = 1
+ data = length 86, hash 554D16CC
+ sample 123:
+ time = 2140000
+ flags = 1
+ data = length 88, hash 99D72771
+ sample 124:
+ time = 2150000
+ flags = 1
+ data = length 88, hash 7176DFBF
+ sample 125:
+ time = 2160000
+ flags = 1
+ data = length 86, hash BAA22669
+ sample 126:
+ time = 2170000
+ flags = 1
+ data = length 88, hash B00B0D3C
+ sample 127:
+ time = 2180000
+ flags = 1
+ data = length 89, hash 73FED83A
+ sample 128:
+ time = 2190000
+ flags = 1
+ data = length 86, hash 4A4138D3
+ sample 129:
+ time = 2200000
+ flags = 1
+ data = length 89, hash E0A860FF
+ sample 130:
+ time = 2210000
+ flags = 1
+ data = length 95, hash EE5A8AED
+ sample 131:
+ time = 2220000
+ flags = 1
+ data = length 92, hash 36DBD7FD
+ sample 132:
+ time = 2230000
+ flags = 1
+ data = length 88, hash EE47A7E4
+ sample 133:
+ time = 2240000
+ flags = 1
+ data = length 100, hash 2E1A603F
+ sample 134:
+ time = 2250000
+ flags = 1
+ data = length 89, hash 657ED4A3
+ sample 135:
+ time = 2260000
+ flags = 1
+ data = length 86, hash A833DC7B
+ sample 136:
+ time = 2270000
+ flags = 1
+ data = length 88, hash 81E80732
+ sample 137:
+ time = 2280000
+ flags = 1
+ data = length 91, hash FA256A0F
+ sample 138:
+ time = 2290000
+ flags = 1
+ data = length 88, hash A63A4DBA
+ sample 139:
+ time = 2300000
+ flags = 1
+ data = length 88, hash 67910A9F
+ sample 140:
+ time = 2310000
+ flags = 1
+ data = length 86, hash EB387DB6
+ sample 141:
+ time = 2320000
+ flags = 1
+ data = length 88, hash 5ACAAC2A
+ sample 142:
+ time = 2330000
+ flags = 1
+ data = length 86, hash 6ADF2E1F
+ sample 143:
+ time = 2340000
+ flags = 1
+ data = length 85, hash 9D064471
+ sample 144:
+ time = 2350000
+ flags = 1
+ data = length 87, hash F176C59
+ sample 145:
+ time = 2360000
+ flags = 1
+ data = length 89, hash 5CA40CE4
+ sample 146:
+ time = 2370000
+ flags = 1
+ data = length 88, hash 67B944FC
+ sample 147:
+ time = 2380000
+ flags = 1
+ data = length 86, hash B3A84EC8
+ sample 148:
+ time = 2390000
+ flags = 1
+ data = length 92, hash A6ACF94B
+ sample 149:
+ time = 2400000
+ flags = 1
+ data = length 88, hash CB0C9730
+ sample 150:
+ time = 2410000
+ flags = 1
+ data = length 88, hash C79FE804
+ sample 151:
+ time = 2420000
+ flags = 1
+ data = length 88, hash A74C7F0A
+ sample 152:
+ time = 2430000
+ flags = 1
+ data = length 91, hash 55F6F0A5
+ sample 153:
+ time = 2440000
+ flags = 1
+ data = length 93, hash 330F33E7
+ sample 154:
+ time = 2450000
+ flags = 1
+ data = length 89, hash 614AFBA0
+ sample 155:
+ time = 2460000
+ flags = 1
+ data = length 87, hash 3CE4652D
+ sample 156:
+ time = 2470000
+ flags = 1
+ data = length 87, hash 4EFD5467
+ sample 157:
+ time = 2480000
+ flags = 1
+ data = length 86, hash D81B3EB8
+ sample 158:
+ time = 2490000
+ flags = 1
+ data = length 88, hash 96CB6871
+ sample 159:
+ time = 2500000
+ flags = 1
+ data = length 88, hash E9DF2786
+ sample 160:
+ time = 2510000
+ flags = 1
+ data = length 89, hash 2CA33D96
+ sample 161:
+ time = 2520000
+ flags = 1
+ data = length 90, hash 96BDE594
+ sample 162:
+ time = 2530000
+ flags = 1
+ data = length 87, hash C261493C
+ sample 163:
+ time = 2540000
+ flags = 1
+ data = length 86, hash D037318E
+ sample 164:
+ time = 2550000
+ flags = 1
+ data = length 88, hash BC15BC88
+ sample 165:
+ time = 2560000
+ flags = 1
+ data = length 91, hash A8361A51
+ sample 166:
+ time = 2570000
+ flags = 1
+ data = length 87, hash 4AFDB5F2
+ sample 167:
+ time = 2580000
+ flags = 1
+ data = length 87, hash 6447F8CB
+ sample 168:
+ time = 2590000
+ flags = 1
+ data = length 89, hash 48305229
+ sample 169:
+ time = 2600000
+ flags = 1
+ data = length 87, hash 8741D9E7
+ sample 170:
+ time = 2610000
+ flags = 1
+ data = length 120, hash 761F020C
+ sample 171:
+ time = 2620000
+ flags = 1
+ data = length 139, hash AECE2E57
+ sample 172:
+ time = 2630000
+ flags = 1
+ data = length 166, hash 6288797A
+ sample 173:
+ time = 2640000
+ flags = 1
+ data = length 144, hash 437821A0
+ sample 174:
+ time = 2650000
+ flags = 1
+ data = length 113, hash FCCBEDF1
+ sample 175:
+ time = 2660000
+ flags = 1
+ data = length 108, hash C4040614
+ sample 176:
+ time = 2670000
+ flags = 1
+ data = length 125, hash E29064C2
+ sample 177:
+ time = 2680000
+ flags = 1
+ data = length 126, hash D42D24FF
+ sample 178:
+ time = 2690000
+ flags = 1
+ data = length 122, hash 30AF267D
+ sample 179:
+ time = 2700000
+ flags = 1
+ data = length 122, hash 45CEC1FB
+ sample 180:
+ time = 2710000
+ flags = 1
+ data = length 134, hash 59143FE2
+ sample 181:
+ time = 2720000
+ flags = 1
+ data = length 134, hash BD52A84
+ sample 182:
+ time = 2730000
+ flags = 1
+ data = length 120, hash 745C3714
+ sample 183:
+ time = 2740000
+ flags = 1
+ data = length 126, hash 505E117B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear.opus.2.dump b/tree/testdata/src/test/assets/ogg/bear.opus.2.dump
new file mode 100644
index 0000000..14d68f3
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear.opus.2.dump
@@ -0,0 +1,388 @@
+seekMap:
+ isSeekable = true
+ duration = 2747500
+ getPosition(0) = [[timeUs=0, position=125]]
+ getPosition(1) = [[timeUs=1, position=125]]
+ getPosition(1373750) = [[timeUs=1373750, position=125]]
+ getPosition(2747500) = [[timeUs=2747500, position=125]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 8698
+ sample count = 92
+ format 0:
+ sampleMimeType = audio/opus
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 19, hash BFE794DB
+ data = length 8, hash CA22068C
+ data = length 8, hash 79C07075
+ sample 0:
+ time = 1830000
+ flags = 1
+ data = length 86, hash 64D21AA1
+ sample 1:
+ time = 1840000
+ flags = 1
+ data = length 86, hash A1FAAF2C
+ sample 2:
+ time = 1850000
+ flags = 1
+ data = length 86, hash ECA80F7E
+ sample 3:
+ time = 1860000
+ flags = 1
+ data = length 86, hash FEB03B2C
+ sample 4:
+ time = 1870000
+ flags = 1
+ data = length 85, hash 2C2E6B2F
+ sample 5:
+ time = 1880000
+ flags = 1
+ data = length 89, hash A0D7AC3
+ sample 6:
+ time = 1890000
+ flags = 1
+ data = length 87, hash 83739547
+ sample 7:
+ time = 1900000
+ flags = 1
+ data = length 86, hash 991E531E
+ sample 8:
+ time = 1910000
+ flags = 1
+ data = length 88, hash 16B287A3
+ sample 9:
+ time = 1920000
+ flags = 1
+ data = length 86, hash FC86EED
+ sample 10:
+ time = 1930000
+ flags = 1
+ data = length 86, hash 96AF0248
+ sample 11:
+ time = 1940000
+ flags = 1
+ data = length 86, hash 288402C8
+ sample 12:
+ time = 1950000
+ flags = 1
+ data = length 87, hash 4BBA7912
+ sample 13:
+ time = 1960000
+ flags = 1
+ data = length 86, hash 4A59C719
+ sample 14:
+ time = 1970000
+ flags = 1
+ data = length 85, hash 906E8187
+ sample 15:
+ time = 1980000
+ flags = 1
+ data = length 90, hash CB8B755D
+ sample 16:
+ time = 1990000
+ flags = 1
+ data = length 87, hash C8E02C
+ sample 17:
+ time = 2000000
+ flags = 1
+ data = length 88, hash ACF4D89A
+ sample 18:
+ time = 2010000
+ flags = 1
+ data = length 86, hash 510FE048
+ sample 19:
+ time = 2020000
+ flags = 1
+ data = length 86, hash 64983E46
+ sample 20:
+ time = 2030000
+ flags = 1
+ data = length 86, hash CEA76A1E
+ sample 21:
+ time = 2040000
+ flags = 1
+ data = length 87, hash AADE498E
+ sample 22:
+ time = 2050000
+ flags = 1
+ data = length 127, hash 353A6D8C
+ sample 23:
+ time = 2060000
+ flags = 1
+ data = length 87, hash 29E18E62
+ sample 24:
+ time = 2070000
+ flags = 1
+ data = length 87, hash 2CF7B30F
+ sample 25:
+ time = 2080000
+ flags = 1
+ data = length 94, hash 758704C3
+ sample 26:
+ time = 2090000
+ flags = 1
+ data = length 88, hash C2153A4C
+ sample 27:
+ time = 2100000
+ flags = 1
+ data = length 86, hash A0A83DA5
+ sample 28:
+ time = 2110000
+ flags = 1
+ data = length 86, hash 41017D7F
+ sample 29:
+ time = 2120000
+ flags = 1
+ data = length 93, hash 686B0CA2
+ sample 30:
+ time = 2130000
+ flags = 1
+ data = length 86, hash 554D16CC
+ sample 31:
+ time = 2140000
+ flags = 1
+ data = length 88, hash 99D72771
+ sample 32:
+ time = 2150000
+ flags = 1
+ data = length 88, hash 7176DFBF
+ sample 33:
+ time = 2160000
+ flags = 1
+ data = length 86, hash BAA22669
+ sample 34:
+ time = 2170000
+ flags = 1
+ data = length 88, hash B00B0D3C
+ sample 35:
+ time = 2180000
+ flags = 1
+ data = length 89, hash 73FED83A
+ sample 36:
+ time = 2190000
+ flags = 1
+ data = length 86, hash 4A4138D3
+ sample 37:
+ time = 2200000
+ flags = 1
+ data = length 89, hash E0A860FF
+ sample 38:
+ time = 2210000
+ flags = 1
+ data = length 95, hash EE5A8AED
+ sample 39:
+ time = 2220000
+ flags = 1
+ data = length 92, hash 36DBD7FD
+ sample 40:
+ time = 2230000
+ flags = 1
+ data = length 88, hash EE47A7E4
+ sample 41:
+ time = 2240000
+ flags = 1
+ data = length 100, hash 2E1A603F
+ sample 42:
+ time = 2250000
+ flags = 1
+ data = length 89, hash 657ED4A3
+ sample 43:
+ time = 2260000
+ flags = 1
+ data = length 86, hash A833DC7B
+ sample 44:
+ time = 2270000
+ flags = 1
+ data = length 88, hash 81E80732
+ sample 45:
+ time = 2280000
+ flags = 1
+ data = length 91, hash FA256A0F
+ sample 46:
+ time = 2290000
+ flags = 1
+ data = length 88, hash A63A4DBA
+ sample 47:
+ time = 2300000
+ flags = 1
+ data = length 88, hash 67910A9F
+ sample 48:
+ time = 2310000
+ flags = 1
+ data = length 86, hash EB387DB6
+ sample 49:
+ time = 2320000
+ flags = 1
+ data = length 88, hash 5ACAAC2A
+ sample 50:
+ time = 2330000
+ flags = 1
+ data = length 86, hash 6ADF2E1F
+ sample 51:
+ time = 2340000
+ flags = 1
+ data = length 85, hash 9D064471
+ sample 52:
+ time = 2350000
+ flags = 1
+ data = length 87, hash F176C59
+ sample 53:
+ time = 2360000
+ flags = 1
+ data = length 89, hash 5CA40CE4
+ sample 54:
+ time = 2370000
+ flags = 1
+ data = length 88, hash 67B944FC
+ sample 55:
+ time = 2380000
+ flags = 1
+ data = length 86, hash B3A84EC8
+ sample 56:
+ time = 2390000
+ flags = 1
+ data = length 92, hash A6ACF94B
+ sample 57:
+ time = 2400000
+ flags = 1
+ data = length 88, hash CB0C9730
+ sample 58:
+ time = 2410000
+ flags = 1
+ data = length 88, hash C79FE804
+ sample 59:
+ time = 2420000
+ flags = 1
+ data = length 88, hash A74C7F0A
+ sample 60:
+ time = 2430000
+ flags = 1
+ data = length 91, hash 55F6F0A5
+ sample 61:
+ time = 2440000
+ flags = 1
+ data = length 93, hash 330F33E7
+ sample 62:
+ time = 2450000
+ flags = 1
+ data = length 89, hash 614AFBA0
+ sample 63:
+ time = 2460000
+ flags = 1
+ data = length 87, hash 3CE4652D
+ sample 64:
+ time = 2470000
+ flags = 1
+ data = length 87, hash 4EFD5467
+ sample 65:
+ time = 2480000
+ flags = 1
+ data = length 86, hash D81B3EB8
+ sample 66:
+ time = 2490000
+ flags = 1
+ data = length 88, hash 96CB6871
+ sample 67:
+ time = 2500000
+ flags = 1
+ data = length 88, hash E9DF2786
+ sample 68:
+ time = 2510000
+ flags = 1
+ data = length 89, hash 2CA33D96
+ sample 69:
+ time = 2520000
+ flags = 1
+ data = length 90, hash 96BDE594
+ sample 70:
+ time = 2530000
+ flags = 1
+ data = length 87, hash C261493C
+ sample 71:
+ time = 2540000
+ flags = 1
+ data = length 86, hash D037318E
+ sample 72:
+ time = 2550000
+ flags = 1
+ data = length 88, hash BC15BC88
+ sample 73:
+ time = 2560000
+ flags = 1
+ data = length 91, hash A8361A51
+ sample 74:
+ time = 2570000
+ flags = 1
+ data = length 87, hash 4AFDB5F2
+ sample 75:
+ time = 2580000
+ flags = 1
+ data = length 87, hash 6447F8CB
+ sample 76:
+ time = 2590000
+ flags = 1
+ data = length 89, hash 48305229
+ sample 77:
+ time = 2600000
+ flags = 1
+ data = length 87, hash 8741D9E7
+ sample 78:
+ time = 2610000
+ flags = 1
+ data = length 120, hash 761F020C
+ sample 79:
+ time = 2620000
+ flags = 1
+ data = length 139, hash AECE2E57
+ sample 80:
+ time = 2630000
+ flags = 1
+ data = length 166, hash 6288797A
+ sample 81:
+ time = 2640000
+ flags = 1
+ data = length 144, hash 437821A0
+ sample 82:
+ time = 2650000
+ flags = 1
+ data = length 113, hash FCCBEDF1
+ sample 83:
+ time = 2660000
+ flags = 1
+ data = length 108, hash C4040614
+ sample 84:
+ time = 2670000
+ flags = 1
+ data = length 125, hash E29064C2
+ sample 85:
+ time = 2680000
+ flags = 1
+ data = length 126, hash D42D24FF
+ sample 86:
+ time = 2690000
+ flags = 1
+ data = length 122, hash 30AF267D
+ sample 87:
+ time = 2700000
+ flags = 1
+ data = length 122, hash 45CEC1FB
+ sample 88:
+ time = 2710000
+ flags = 1
+ data = length 134, hash 59143FE2
+ sample 89:
+ time = 2720000
+ flags = 1
+ data = length 134, hash BD52A84
+ sample 90:
+ time = 2730000
+ flags = 1
+ data = length 120, hash 745C3714
+ sample 91:
+ time = 2740000
+ flags = 1
+ data = length 126, hash 505E117B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear.opus.3.dump b/tree/testdata/src/test/assets/ogg/bear.opus.3.dump
new file mode 100644
index 0000000..b4d5e82
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear.opus.3.dump
@@ -0,0 +1,24 @@
+seekMap:
+ isSeekable = true
+ duration = 2747500
+ getPosition(0) = [[timeUs=0, position=125]]
+ getPosition(1) = [[timeUs=1, position=125]]
+ getPosition(1373750) = [[timeUs=1373750, position=125]]
+ getPosition(2747500) = [[timeUs=2747500, position=125]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 126
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/opus
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 19, hash BFE794DB
+ data = length 8, hash CA22068C
+ data = length 8, hash 79C07075
+ sample 0:
+ time = 2741000
+ flags = 1
+ data = length 126, hash 505E117B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear.opus.unknown_length.dump b/tree/testdata/src/test/assets/ogg/bear.opus.unknown_length.dump
new file mode 100644
index 0000000..aa34535
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear.opus.unknown_length.dump
@@ -0,0 +1,1117 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 25541
+ sample count = 275
+ format 0:
+ sampleMimeType = audio/opus
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 19, hash BFE794DB
+ data = length 8, hash CA22068C
+ data = length 8, hash 79C07075
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 234, hash B77BFFDA
+ sample 1:
+ time = 10000
+ flags = 1
+ data = length 165, hash F7B07C35
+ sample 2:
+ time = 20000
+ flags = 1
+ data = length 100, hash 21AFA81F
+ sample 3:
+ time = 30000
+ flags = 1
+ data = length 85, hash 9180DC2F
+ sample 4:
+ time = 40000
+ flags = 1
+ data = length 85, hash 6AE958C
+ sample 5:
+ time = 50000
+ flags = 1
+ data = length 86, hash C1C5AE60
+ sample 6:
+ time = 60000
+ flags = 1
+ data = length 87, hash B9BD2620
+ sample 7:
+ time = 70000
+ flags = 1
+ data = length 86, hash 5E69E6F9
+ sample 8:
+ time = 80000
+ flags = 1
+ data = length 90, hash C44C7DD9
+ sample 9:
+ time = 90000
+ flags = 1
+ data = length 86, hash C3FCDC6F
+ sample 10:
+ time = 100000
+ flags = 1
+ data = length 86, hash 44EA03BA
+ sample 11:
+ time = 110000
+ flags = 1
+ data = length 160, hash 9F4E1AE8
+ sample 12:
+ time = 120000
+ flags = 1
+ data = length 89, hash 31234460
+ sample 13:
+ time = 130000
+ flags = 1
+ data = length 91, hash 45012D79
+ sample 14:
+ time = 140000
+ flags = 1
+ data = length 90, hash B3E3AC75
+ sample 15:
+ time = 150000
+ flags = 1
+ data = length 87, hash B83B756B
+ sample 16:
+ time = 160000
+ flags = 1
+ data = length 86, hash 383921EB
+ sample 17:
+ time = 170000
+ flags = 1
+ data = length 97, hash 959AD270
+ sample 18:
+ time = 180000
+ flags = 1
+ data = length 92, hash 46C74FA8
+ sample 19:
+ time = 190000
+ flags = 1
+ data = length 91, hash CEA401DD
+ sample 20:
+ time = 200000
+ flags = 1
+ data = length 89, hash 48C41853
+ sample 21:
+ time = 210000
+ flags = 1
+ data = length 90, hash F23245BD
+ sample 22:
+ time = 220000
+ flags = 1
+ data = length 96, hash 95E8985D
+ sample 23:
+ time = 230000
+ flags = 1
+ data = length 96, hash 34295492
+ sample 24:
+ time = 240000
+ flags = 1
+ data = length 94, hash 4E3C9C0F
+ sample 25:
+ time = 250000
+ flags = 1
+ data = length 89, hash 28B74A29
+ sample 26:
+ time = 260000
+ flags = 1
+ data = length 87, hash BAC119A7
+ sample 27:
+ time = 270000
+ flags = 1
+ data = length 88, hash 7139FF38
+ sample 28:
+ time = 280000
+ flags = 1
+ data = length 85, hash 246E1D2A
+ sample 29:
+ time = 290000
+ flags = 1
+ data = length 86, hash 488A0900
+ sample 30:
+ time = 300000
+ flags = 1
+ data = length 90, hash 16FD17B1
+ sample 31:
+ time = 310000
+ flags = 1
+ data = length 87, hash 20E849D9
+ sample 32:
+ time = 320000
+ flags = 1
+ data = length 86, hash 23A0E9BA
+ sample 33:
+ time = 330000
+ flags = 1
+ data = length 87, hash EC935537
+ sample 34:
+ time = 340000
+ flags = 1
+ data = length 92, hash 4D9935AD
+ sample 35:
+ time = 350000
+ flags = 1
+ data = length 87, hash DEDE3FA
+ sample 36:
+ time = 360000
+ flags = 1
+ data = length 87, hash ADC25A6E
+ sample 37:
+ time = 370000
+ flags = 1
+ data = length 88, hash A1C828C5
+ sample 38:
+ time = 380000
+ flags = 1
+ data = length 89, hash 735C087A
+ sample 39:
+ time = 390000
+ flags = 1
+ data = length 89, hash 19AF5D10
+ sample 40:
+ time = 400000
+ flags = 1
+ data = length 90, hash BCCEA2BB
+ sample 41:
+ time = 410000
+ flags = 1
+ data = length 86, hash A7C934A0
+ sample 42:
+ time = 420000
+ flags = 1
+ data = length 86, hash 28BBC0A8
+ sample 43:
+ time = 430000
+ flags = 1
+ data = length 85, hash E60BB12D
+ sample 44:
+ time = 440000
+ flags = 1
+ data = length 141, hash 1D2B8920
+ sample 45:
+ time = 450000
+ flags = 1
+ data = length 121, hash 8AA3E19C
+ sample 46:
+ time = 460000
+ flags = 1
+ data = length 86, hash 24DF0F37
+ sample 47:
+ time = 470000
+ flags = 1
+ data = length 86, hash 1D1775FF
+ sample 48:
+ time = 480000
+ flags = 1
+ data = length 87, hash 5230399E
+ sample 49:
+ time = 490000
+ flags = 1
+ data = length 91, hash 6CD98305
+ sample 50:
+ time = 500000
+ flags = 1
+ data = length 88, hash 4069FBB
+ sample 51:
+ time = 510000
+ flags = 1
+ data = length 89, hash 76824ABF
+ sample 52:
+ time = 520000
+ flags = 1
+ data = length 87, hash BC1B1322
+ sample 53:
+ time = 530000
+ flags = 1
+ data = length 102, hash E01BA053
+ sample 54:
+ time = 540000
+ flags = 1
+ data = length 85, hash C09B626D
+ sample 55:
+ time = 550000
+ flags = 1
+ data = length 88, hash 6B7B404A
+ sample 56:
+ time = 560000
+ flags = 1
+ data = length 85, hash 74A15DC7
+ sample 57:
+ time = 570000
+ flags = 1
+ data = length 88, hash 38DB82E5
+ sample 58:
+ time = 580000
+ flags = 1
+ data = length 86, hash 1A39C081
+ sample 59:
+ time = 590000
+ flags = 1
+ data = length 87, hash 39FEC92
+ sample 60:
+ time = 600000
+ flags = 1
+ data = length 92, hash 278EA09
+ sample 61:
+ time = 610000
+ flags = 1
+ data = length 87, hash 28265F2D
+ sample 62:
+ time = 620000
+ flags = 1
+ data = length 86, hash CC2040C6
+ sample 63:
+ time = 630000
+ flags = 1
+ data = length 138, hash 9E07BC1F
+ sample 64:
+ time = 640000
+ flags = 1
+ data = length 85, hash 4F299670
+ sample 65:
+ time = 650000
+ flags = 1
+ data = length 125, hash B61123C3
+ sample 66:
+ time = 660000
+ flags = 1
+ data = length 89, hash 5CC688ED
+ sample 67:
+ time = 670000
+ flags = 1
+ data = length 88, hash 84AF64A6
+ sample 68:
+ time = 680000
+ flags = 1
+ data = length 89, hash A9BFC8DC
+ sample 69:
+ time = 690000
+ flags = 1
+ data = length 90, hash 2FF77060
+ sample 70:
+ time = 700000
+ flags = 1
+ data = length 96, hash E11AFD61
+ sample 71:
+ time = 710000
+ flags = 1
+ data = length 87, hash 85D14EDA
+ sample 72:
+ time = 720000
+ flags = 1
+ data = length 88, hash 5FC71D53
+ sample 73:
+ time = 730000
+ flags = 1
+ data = length 89, hash 957187B6
+ sample 74:
+ time = 740000
+ flags = 1
+ data = length 89, hash 5A776082
+ sample 75:
+ time = 750000
+ flags = 1
+ data = length 87, hash E8A83AFE
+ sample 76:
+ time = 760000
+ flags = 1
+ data = length 87, hash F1989133
+ sample 77:
+ time = 770000
+ flags = 1
+ data = length 87, hash FA06BCCA
+ sample 78:
+ time = 780000
+ flags = 1
+ data = length 86, hash BD97E0C0
+ sample 79:
+ time = 790000
+ flags = 1
+ data = length 88, hash E6AE022C
+ sample 80:
+ time = 800000
+ flags = 1
+ data = length 87, hash FB6C6169
+ sample 81:
+ time = 810000
+ flags = 1
+ data = length 87, hash DFFCD2CF
+ sample 82:
+ time = 820000
+ flags = 1
+ data = length 88, hash A4B5EB52
+ sample 83:
+ time = 830000
+ flags = 1
+ data = length 85, hash A63CF4EA
+ sample 84:
+ time = 840000
+ flags = 1
+ data = length 86, hash F126E7C7
+ sample 85:
+ time = 850000
+ flags = 1
+ data = length 86, hash 21A8B22F
+ sample 86:
+ time = 860000
+ flags = 1
+ data = length 87, hash 6520E7C1
+ sample 87:
+ time = 870000
+ flags = 1
+ data = length 88, hash 825B7423
+ sample 88:
+ time = 880000
+ flags = 1
+ data = length 88, hash DF3DBD48
+ sample 89:
+ time = 890000
+ flags = 1
+ data = length 87, hash B32C68D0
+ sample 90:
+ time = 900000
+ flags = 1
+ data = length 89, hash B99DFFCA
+ sample 91:
+ time = 910000
+ flags = 1
+ data = length 88, hash 9C8D5178
+ sample 92:
+ time = 920000
+ flags = 1
+ data = length 88, hash 48A0B19A
+ sample 93:
+ time = 930000
+ flags = 1
+ data = length 88, hash B62C94DD
+ sample 94:
+ time = 940000
+ flags = 1
+ data = length 92, hash 96DBDD46
+ sample 95:
+ time = 950000
+ flags = 1
+ data = length 87, hash 7B80E6F
+ sample 96:
+ time = 960000
+ flags = 1
+ data = length 86, hash 9C60225B
+ sample 97:
+ time = 970000
+ flags = 1
+ data = length 87, hash 45F7E6E8
+ sample 98:
+ time = 980000
+ flags = 1
+ data = length 87, hash DDC2D592
+ sample 99:
+ time = 990000
+ flags = 1
+ data = length 91, hash 173D3B26
+ sample 100:
+ time = 1000000
+ flags = 1
+ data = length 87, hash CF3629DF
+ sample 101:
+ time = 1010000
+ flags = 1
+ data = length 87, hash BBE2E7B3
+ sample 102:
+ time = 1020000
+ flags = 1
+ data = length 89, hash 89AFFB10
+ sample 103:
+ time = 1030000
+ flags = 1
+ data = length 88, hash 510DCC90
+ sample 104:
+ time = 1040000
+ flags = 1
+ data = length 88, hash CBA56E5F
+ sample 105:
+ time = 1050000
+ flags = 1
+ data = length 87, hash B4B1B3FF
+ sample 106:
+ time = 1060000
+ flags = 1
+ data = length 89, hash B976A537
+ sample 107:
+ time = 1070000
+ flags = 1
+ data = length 96, hash 43ECF2C1
+ sample 108:
+ time = 1080000
+ flags = 1
+ data = length 90, hash BB7ECB44
+ sample 109:
+ time = 1090000
+ flags = 1
+ data = length 89, hash B8E221A5
+ sample 110:
+ time = 1100000
+ flags = 1
+ data = length 86, hash B35BEF5B
+ sample 111:
+ time = 1110000
+ flags = 1
+ data = length 89, hash 9002F0EC
+ sample 112:
+ time = 1120000
+ flags = 1
+ data = length 85, hash F694B20
+ sample 113:
+ time = 1130000
+ flags = 1
+ data = length 87, hash D7CC386E
+ sample 114:
+ time = 1140000
+ flags = 1
+ data = length 89, hash EE9E0E79
+ sample 115:
+ time = 1150000
+ flags = 1
+ data = length 90, hash CA72C96B
+ sample 116:
+ time = 1160000
+ flags = 1
+ data = length 112, hash 4AD3D1B1
+ sample 117:
+ time = 1170000
+ flags = 1
+ data = length 87, hash FA568FAB
+ sample 118:
+ time = 1180000
+ flags = 1
+ data = length 90, hash 6E6948D2
+ sample 119:
+ time = 1190000
+ flags = 1
+ data = length 89, hash 5465A762
+ sample 120:
+ time = 1200000
+ flags = 1
+ data = length 87, hash BED19B40
+ sample 121:
+ time = 1210000
+ flags = 1
+ data = length 89, hash 5D05836A
+ sample 122:
+ time = 1220000
+ flags = 1
+ data = length 87, hash A8A3EF5A
+ sample 123:
+ time = 1230000
+ flags = 1
+ data = length 90, hash 6425A77A
+ sample 124:
+ time = 1240000
+ flags = 1
+ data = length 92, hash 7F320FA
+ sample 125:
+ time = 1250000
+ flags = 1
+ data = length 89, hash 2C7837D6
+ sample 126:
+ time = 1260000
+ flags = 1
+ data = length 86, hash 58D56685
+ sample 127:
+ time = 1270000
+ flags = 1
+ data = length 91, hash ADC5072F
+ sample 128:
+ time = 1280000
+ flags = 1
+ data = length 85, hash 53EFD93
+ sample 129:
+ time = 1290000
+ flags = 1
+ data = length 87, hash D006B535
+ sample 130:
+ time = 1300000
+ flags = 1
+ data = length 86, hash AE944625
+ sample 131:
+ time = 1310000
+ flags = 1
+ data = length 89, hash B5D3C81D
+ sample 132:
+ time = 1320000
+ flags = 1
+ data = length 86, hash 3BB1D0E7
+ sample 133:
+ time = 1330000
+ flags = 1
+ data = length 102, hash 16EEC441
+ sample 134:
+ time = 1340000
+ flags = 1
+ data = length 90, hash 1005B936
+ sample 135:
+ time = 1350000
+ flags = 1
+ data = length 85, hash 15EEBF9A
+ sample 136:
+ time = 1360000
+ flags = 1
+ data = length 87, hash 37C83AC2
+ sample 137:
+ time = 1370000
+ flags = 1
+ data = length 85, hash 2D27855D
+ sample 138:
+ time = 1380000
+ flags = 1
+ data = length 85, hash 753EB7C6
+ sample 139:
+ time = 1390000
+ flags = 1
+ data = length 91, hash C0813318
+ sample 140:
+ time = 1400000
+ flags = 1
+ data = length 89, hash 3A6468AC
+ sample 141:
+ time = 1410000
+ flags = 1
+ data = length 88, hash 3D220ABC
+ sample 142:
+ time = 1420000
+ flags = 1
+ data = length 140, hash 7949ABC7
+ sample 143:
+ time = 1430000
+ flags = 1
+ data = length 92, hash F19AFA45
+ sample 144:
+ time = 1440000
+ flags = 1
+ data = length 90, hash 3D21587C
+ sample 145:
+ time = 1450000
+ flags = 1
+ data = length 89, hash 5C12226C
+ sample 146:
+ time = 1460000
+ flags = 1
+ data = length 90, hash 22BA14FC
+ sample 147:
+ time = 1470000
+ flags = 1
+ data = length 88, hash F064B21C
+ sample 148:
+ time = 1480000
+ flags = 1
+ data = length 87, hash 6D7906B9
+ sample 149:
+ time = 1490000
+ flags = 1
+ data = length 88, hash 6756A484
+ sample 150:
+ time = 1500000
+ flags = 1
+ data = length 91, hash C95C00B6
+ sample 151:
+ time = 1510000
+ flags = 1
+ data = length 87, hash 728D8119
+ sample 152:
+ time = 1520000
+ flags = 1
+ data = length 90, hash C43DA1B4
+ sample 153:
+ time = 1530000
+ flags = 1
+ data = length 88, hash C181BB57
+ sample 154:
+ time = 1540000
+ flags = 1
+ data = length 84, hash F75B1639
+ sample 155:
+ time = 1550000
+ flags = 1
+ data = length 87, hash B6F32978
+ sample 156:
+ time = 1560000
+ flags = 1
+ data = length 90, hash 36D6E2D7
+ sample 157:
+ time = 1570000
+ flags = 1
+ data = length 87, hash 4C9657A7
+ sample 158:
+ time = 1580000
+ flags = 1
+ data = length 89, hash C3BDB9B7
+ sample 159:
+ time = 1590000
+ flags = 1
+ data = length 88, hash DB51087E
+ sample 160:
+ time = 1600000
+ flags = 1
+ data = length 86, hash 1550F998
+ sample 161:
+ time = 1610000
+ flags = 1
+ data = length 86, hash A445FAD4
+ sample 162:
+ time = 1620000
+ flags = 1
+ data = length 85, hash 60D3362C
+ sample 163:
+ time = 1630000
+ flags = 1
+ data = length 172, hash 945D63E4
+ sample 164:
+ time = 1640000
+ flags = 1
+ data = length 107, hash 585B7C04
+ sample 165:
+ time = 1650000
+ flags = 1
+ data = length 110, hash 74BECF69
+ sample 166:
+ time = 1660000
+ flags = 1
+ data = length 87, hash 63DE1D24
+ sample 167:
+ time = 1670000
+ flags = 1
+ data = length 90, hash 1C1D28DB
+ sample 168:
+ time = 1680000
+ flags = 1
+ data = length 87, hash CB382A67
+ sample 169:
+ time = 1690000
+ flags = 1
+ data = length 85, hash A227BA13
+ sample 170:
+ time = 1700000
+ flags = 1
+ data = length 86, hash EFD8B10B
+ sample 171:
+ time = 1710000
+ flags = 1
+ data = length 87, hash 47FF364A
+ sample 172:
+ time = 1720000
+ flags = 1
+ data = length 91, hash 31D4B48A
+ sample 173:
+ time = 1730000
+ flags = 1
+ data = length 91, hash DD69BD85
+ sample 174:
+ time = 1740000
+ flags = 1
+ data = length 88, hash AF1A95C6
+ sample 175:
+ time = 1750000
+ flags = 1
+ data = length 87, hash 2FB8AF74
+ sample 176:
+ time = 1760000
+ flags = 1
+ data = length 92, hash 173C707A
+ sample 177:
+ time = 1770000
+ flags = 1
+ data = length 88, hash 5F58F5E8
+ sample 178:
+ time = 1780000
+ flags = 1
+ data = length 91, hash D449785F
+ sample 179:
+ time = 1790000
+ flags = 1
+ data = length 91, hash CE2CB465
+ sample 180:
+ time = 1800000
+ flags = 1
+ data = length 93, hash ABC1C62E
+ sample 181:
+ time = 1810000
+ flags = 1
+ data = length 87, hash 83B4B9A0
+ sample 182:
+ time = 1820000
+ flags = 1
+ data = length 87, hash 3220D562
+ sample 183:
+ time = 1830000
+ flags = 1
+ data = length 86, hash 64D21AA1
+ sample 184:
+ time = 1840000
+ flags = 1
+ data = length 86, hash A1FAAF2C
+ sample 185:
+ time = 1850000
+ flags = 1
+ data = length 86, hash ECA80F7E
+ sample 186:
+ time = 1860000
+ flags = 1
+ data = length 86, hash FEB03B2C
+ sample 187:
+ time = 1870000
+ flags = 1
+ data = length 85, hash 2C2E6B2F
+ sample 188:
+ time = 1880000
+ flags = 1
+ data = length 89, hash A0D7AC3
+ sample 189:
+ time = 1890000
+ flags = 1
+ data = length 87, hash 83739547
+ sample 190:
+ time = 1900000
+ flags = 1
+ data = length 86, hash 991E531E
+ sample 191:
+ time = 1910000
+ flags = 1
+ data = length 88, hash 16B287A3
+ sample 192:
+ time = 1920000
+ flags = 1
+ data = length 86, hash FC86EED
+ sample 193:
+ time = 1930000
+ flags = 1
+ data = length 86, hash 96AF0248
+ sample 194:
+ time = 1940000
+ flags = 1
+ data = length 86, hash 288402C8
+ sample 195:
+ time = 1950000
+ flags = 1
+ data = length 87, hash 4BBA7912
+ sample 196:
+ time = 1960000
+ flags = 1
+ data = length 86, hash 4A59C719
+ sample 197:
+ time = 1970000
+ flags = 1
+ data = length 85, hash 906E8187
+ sample 198:
+ time = 1980000
+ flags = 1
+ data = length 90, hash CB8B755D
+ sample 199:
+ time = 1990000
+ flags = 1
+ data = length 87, hash C8E02C
+ sample 200:
+ time = 2000000
+ flags = 1
+ data = length 88, hash ACF4D89A
+ sample 201:
+ time = 2010000
+ flags = 1
+ data = length 86, hash 510FE048
+ sample 202:
+ time = 2020000
+ flags = 1
+ data = length 86, hash 64983E46
+ sample 203:
+ time = 2030000
+ flags = 1
+ data = length 86, hash CEA76A1E
+ sample 204:
+ time = 2040000
+ flags = 1
+ data = length 87, hash AADE498E
+ sample 205:
+ time = 2050000
+ flags = 1
+ data = length 127, hash 353A6D8C
+ sample 206:
+ time = 2060000
+ flags = 1
+ data = length 87, hash 29E18E62
+ sample 207:
+ time = 2070000
+ flags = 1
+ data = length 87, hash 2CF7B30F
+ sample 208:
+ time = 2080000
+ flags = 1
+ data = length 94, hash 758704C3
+ sample 209:
+ time = 2090000
+ flags = 1
+ data = length 88, hash C2153A4C
+ sample 210:
+ time = 2100000
+ flags = 1
+ data = length 86, hash A0A83DA5
+ sample 211:
+ time = 2110000
+ flags = 1
+ data = length 86, hash 41017D7F
+ sample 212:
+ time = 2120000
+ flags = 1
+ data = length 93, hash 686B0CA2
+ sample 213:
+ time = 2130000
+ flags = 1
+ data = length 86, hash 554D16CC
+ sample 214:
+ time = 2140000
+ flags = 1
+ data = length 88, hash 99D72771
+ sample 215:
+ time = 2150000
+ flags = 1
+ data = length 88, hash 7176DFBF
+ sample 216:
+ time = 2160000
+ flags = 1
+ data = length 86, hash BAA22669
+ sample 217:
+ time = 2170000
+ flags = 1
+ data = length 88, hash B00B0D3C
+ sample 218:
+ time = 2180000
+ flags = 1
+ data = length 89, hash 73FED83A
+ sample 219:
+ time = 2190000
+ flags = 1
+ data = length 86, hash 4A4138D3
+ sample 220:
+ time = 2200000
+ flags = 1
+ data = length 89, hash E0A860FF
+ sample 221:
+ time = 2210000
+ flags = 1
+ data = length 95, hash EE5A8AED
+ sample 222:
+ time = 2220000
+ flags = 1
+ data = length 92, hash 36DBD7FD
+ sample 223:
+ time = 2230000
+ flags = 1
+ data = length 88, hash EE47A7E4
+ sample 224:
+ time = 2240000
+ flags = 1
+ data = length 100, hash 2E1A603F
+ sample 225:
+ time = 2250000
+ flags = 1
+ data = length 89, hash 657ED4A3
+ sample 226:
+ time = 2260000
+ flags = 1
+ data = length 86, hash A833DC7B
+ sample 227:
+ time = 2270000
+ flags = 1
+ data = length 88, hash 81E80732
+ sample 228:
+ time = 2280000
+ flags = 1
+ data = length 91, hash FA256A0F
+ sample 229:
+ time = 2290000
+ flags = 1
+ data = length 88, hash A63A4DBA
+ sample 230:
+ time = 2300000
+ flags = 1
+ data = length 88, hash 67910A9F
+ sample 231:
+ time = 2310000
+ flags = 1
+ data = length 86, hash EB387DB6
+ sample 232:
+ time = 2320000
+ flags = 1
+ data = length 88, hash 5ACAAC2A
+ sample 233:
+ time = 2330000
+ flags = 1
+ data = length 86, hash 6ADF2E1F
+ sample 234:
+ time = 2340000
+ flags = 1
+ data = length 85, hash 9D064471
+ sample 235:
+ time = 2350000
+ flags = 1
+ data = length 87, hash F176C59
+ sample 236:
+ time = 2360000
+ flags = 1
+ data = length 89, hash 5CA40CE4
+ sample 237:
+ time = 2370000
+ flags = 1
+ data = length 88, hash 67B944FC
+ sample 238:
+ time = 2380000
+ flags = 1
+ data = length 86, hash B3A84EC8
+ sample 239:
+ time = 2390000
+ flags = 1
+ data = length 92, hash A6ACF94B
+ sample 240:
+ time = 2400000
+ flags = 1
+ data = length 88, hash CB0C9730
+ sample 241:
+ time = 2410000
+ flags = 1
+ data = length 88, hash C79FE804
+ sample 242:
+ time = 2420000
+ flags = 1
+ data = length 88, hash A74C7F0A
+ sample 243:
+ time = 2430000
+ flags = 1
+ data = length 91, hash 55F6F0A5
+ sample 244:
+ time = 2440000
+ flags = 1
+ data = length 93, hash 330F33E7
+ sample 245:
+ time = 2450000
+ flags = 1
+ data = length 89, hash 614AFBA0
+ sample 246:
+ time = 2460000
+ flags = 1
+ data = length 87, hash 3CE4652D
+ sample 247:
+ time = 2470000
+ flags = 1
+ data = length 87, hash 4EFD5467
+ sample 248:
+ time = 2480000
+ flags = 1
+ data = length 86, hash D81B3EB8
+ sample 249:
+ time = 2490000
+ flags = 1
+ data = length 88, hash 96CB6871
+ sample 250:
+ time = 2500000
+ flags = 1
+ data = length 88, hash E9DF2786
+ sample 251:
+ time = 2510000
+ flags = 1
+ data = length 89, hash 2CA33D96
+ sample 252:
+ time = 2520000
+ flags = 1
+ data = length 90, hash 96BDE594
+ sample 253:
+ time = 2530000
+ flags = 1
+ data = length 87, hash C261493C
+ sample 254:
+ time = 2540000
+ flags = 1
+ data = length 86, hash D037318E
+ sample 255:
+ time = 2550000
+ flags = 1
+ data = length 88, hash BC15BC88
+ sample 256:
+ time = 2560000
+ flags = 1
+ data = length 91, hash A8361A51
+ sample 257:
+ time = 2570000
+ flags = 1
+ data = length 87, hash 4AFDB5F2
+ sample 258:
+ time = 2580000
+ flags = 1
+ data = length 87, hash 6447F8CB
+ sample 259:
+ time = 2590000
+ flags = 1
+ data = length 89, hash 48305229
+ sample 260:
+ time = 2600000
+ flags = 1
+ data = length 87, hash 8741D9E7
+ sample 261:
+ time = 2610000
+ flags = 1
+ data = length 120, hash 761F020C
+ sample 262:
+ time = 2620000
+ flags = 1
+ data = length 139, hash AECE2E57
+ sample 263:
+ time = 2630000
+ flags = 1
+ data = length 166, hash 6288797A
+ sample 264:
+ time = 2640000
+ flags = 1
+ data = length 144, hash 437821A0
+ sample 265:
+ time = 2650000
+ flags = 1
+ data = length 113, hash FCCBEDF1
+ sample 266:
+ time = 2660000
+ flags = 1
+ data = length 108, hash C4040614
+ sample 267:
+ time = 2670000
+ flags = 1
+ data = length 125, hash E29064C2
+ sample 268:
+ time = 2680000
+ flags = 1
+ data = length 126, hash D42D24FF
+ sample 269:
+ time = 2690000
+ flags = 1
+ data = length 122, hash 30AF267D
+ sample 270:
+ time = 2700000
+ flags = 1
+ data = length 122, hash 45CEC1FB
+ sample 271:
+ time = 2710000
+ flags = 1
+ data = length 134, hash 59143FE2
+ sample 272:
+ time = 2720000
+ flags = 1
+ data = length 134, hash BD52A84
+ sample 273:
+ time = 2730000
+ flags = 1
+ data = length 120, hash 745C3714
+ sample 274:
+ time = 2740000
+ flags = 1
+ data = length 126, hash 505E117B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac.ogg b/tree/testdata/src/test/assets/ogg/bear_flac.ogg
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/bear_flac.ogg
rename to tree/testdata/src/test/assets/ogg/bear_flac.ogg
Binary files differ
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac.ogg.0.dump b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.0.dump
new file mode 100644
index 0000000..f303cda
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.0.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8457]]
+ getPosition(1) = [[timeUs=0, position=8457]]
+ getPosition(1370500) = [[timeUs=0, position=8457]]
+ getPosition(2741000) = [[timeUs=0, position=8457]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac.ogg.1.dump b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.1.dump
new file mode 100644
index 0000000..0d4ab25
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.1.dump
@@ -0,0 +1,111 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8457]]
+ getPosition(1) = [[timeUs=0, position=8457]]
+ getPosition(1370500) = [[timeUs=0, position=8457]]
+ getPosition(2741000) = [[timeUs=0, position=8457]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac.ogg.2.dump b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.2.dump
new file mode 100644
index 0000000..9a03aa3
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.2.dump
@@ -0,0 +1,67 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8457]]
+ getPosition(1) = [[timeUs=0, position=8457]]
+ getPosition(1370500) = [[timeUs=0, position=8457]]
+ getPosition(2741000) = [[timeUs=0, position=8457]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac.ogg.3.dump b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.3.dump
new file mode 100644
index 0000000..ed1ca03
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8457]]
+ getPosition(1) = [[timeUs=0, position=8457]]
+ getPosition(1370500) = [[timeUs=0, position=8457]]
+ getPosition(2741000) = [[timeUs=0, position=8457]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 445
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac.ogg.unknown_length.dump b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.unknown_length.dump
new file mode 100644
index 0000000..f303cda
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac.ogg.unknown_length.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8457]]
+ getPosition(1) = [[timeUs=0, position=8457]]
+ getPosition(1370500) = [[timeUs=0, position=8457]]
+ getPosition(2741000) = [[timeUs=0, position=8457]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/bear_flac_noseektable.ogg
rename to tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg
Binary files differ
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump
new file mode 100644
index 0000000..101b6db
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.0.dump
@@ -0,0 +1,151 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8407]]
+ getPosition(1) = [[timeUs=1, position=8407]]
+ getPosition(1370500) = [[timeUs=1370500, position=61076]]
+ getPosition(2741000) = [[timeUs=2741000, position=143746]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump
new file mode 100644
index 0000000..f90ed83
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.1.dump
@@ -0,0 +1,111 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8407]]
+ getPosition(1) = [[timeUs=1, position=8407]]
+ getPosition(1370500) = [[timeUs=1370500, position=61076]]
+ getPosition(2741000) = [[timeUs=2741000, position=143746]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 113666
+ sample count = 23
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 1:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 2:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 3:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 4:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 5:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 6:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 7:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 8:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 9:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 10:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 11:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 12:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 13:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 14:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 15:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 16:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 17:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 18:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 19:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 20:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 21:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 22:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump
new file mode 100644
index 0000000..3bef927
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.2.dump
@@ -0,0 +1,67 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8407]]
+ getPosition(1) = [[timeUs=1, position=8407]]
+ getPosition(1370500) = [[timeUs=1370500, position=61076]]
+ getPosition(2741000) = [[timeUs=2741000, position=143746]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 55652
+ sample count = 12
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 1:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 2:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 3:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 4:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 5:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 6:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 7:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 8:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 9:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 10:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 11:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump
new file mode 100644
index 0000000..1916e6b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.3.dump
@@ -0,0 +1,23 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=8407]]
+ getPosition(1) = [[timeUs=1, position=8407]]
+ getPosition(1370500) = [[timeUs=1370500, position=61076]]
+ getPosition(2741000) = [[timeUs=2741000, position=143746]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 445
+ sample count = 1
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.unknown_length.dump b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.unknown_length.dump
new file mode 100644
index 0000000..bc49c37
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_flac_noseektable.ogg.unknown_length.dump
@@ -0,0 +1,148 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 164431
+ sample count = 33
+ format 0:
+ sampleMimeType = audio/flac
+ maxInputSize = 5776
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 42, hash 83F6895
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 5030, hash D2B60530
+ sample 1:
+ time = 85333
+ flags = 1
+ data = length 5066, hash 4C932A54
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 5112, hash 7E5A7B61
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 5044, hash 7EF93F13
+ sample 4:
+ time = 341333
+ flags = 1
+ data = length 4943, hash DE7E27F8
+ sample 5:
+ time = 426666
+ flags = 1
+ data = length 5121, hash 6D0D0B40
+ sample 6:
+ time = 512000
+ flags = 1
+ data = length 5068, hash 9924644F
+ sample 7:
+ time = 597333
+ flags = 1
+ data = length 5143, hash 6C34F0CE
+ sample 8:
+ time = 682666
+ flags = 1
+ data = length 5109, hash E3B7BEFB
+ sample 9:
+ time = 768000
+ flags = 1
+ data = length 5129, hash 44111D9B
+ sample 10:
+ time = 853333
+ flags = 1
+ data = length 5031, hash 9D55EA53
+ sample 11:
+ time = 938666
+ flags = 1
+ data = length 5119, hash E1CB9BA6
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 5360, hash 17265C5D
+ sample 13:
+ time = 1109333
+ flags = 1
+ data = length 5340, hash A90FDDF1
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 5162, hash 31F65AD5
+ sample 15:
+ time = 1280000
+ flags = 1
+ data = length 5168, hash F2394F2D
+ sample 16:
+ time = 1365333
+ flags = 1
+ data = length 5776, hash 58437AB3
+ sample 17:
+ time = 1450666
+ flags = 1
+ data = length 5394, hash EBAB20A8
+ sample 18:
+ time = 1536000
+ flags = 1
+ data = length 5168, hash BF37C7A5
+ sample 19:
+ time = 1621333
+ flags = 1
+ data = length 5324, hash 59546B7B
+ sample 20:
+ time = 1706666
+ flags = 1
+ data = length 5172, hash 6036EF0B
+ sample 21:
+ time = 1792000
+ flags = 1
+ data = length 5102, hash 5A131071
+ sample 22:
+ time = 1877333
+ flags = 1
+ data = length 5111, hash 3D9EBB3B
+ sample 23:
+ time = 1962666
+ flags = 1
+ data = length 5113, hash 61101D4F
+ sample 24:
+ time = 2048000
+ flags = 1
+ data = length 5229, hash D2E55742
+ sample 25:
+ time = 2133333
+ flags = 1
+ data = length 5162, hash 7F2E97FA
+ sample 26:
+ time = 2218666
+ flags = 1
+ data = length 5255, hash D92A782
+ sample 27:
+ time = 2304000
+ flags = 1
+ data = length 5196, hash 98FE5138
+ sample 28:
+ time = 2389333
+ flags = 1
+ data = length 5214, hash 3D35C38C
+ sample 29:
+ time = 2474666
+ flags = 1
+ data = length 5211, hash 7E25420F
+ sample 30:
+ time = 2560000
+ flags = 1
+ data = length 5230, hash 2AD96FBC
+ sample 31:
+ time = 2645333
+ flags = 1
+ data = length 3384, hash 938BCDD9
+ sample 32:
+ time = 2730666
+ flags = 1
+ data = length 445, hash A388E3D6
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/bear_vorbis.ogg
rename to tree/testdata/src/test/assets/ogg/bear_vorbis.ogg
Binary files differ
diff --git a/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.0.dump b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.0.dump
new file mode 100644
index 0000000..7c00eb0
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.0.dump
@@ -0,0 +1,740 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=3995]]
+ getPosition(1) = [[timeUs=1, position=3995]]
+ getPosition(1370500) = [[timeUs=1370500, position=3995]]
+ getPosition(2741000) = [[timeUs=2741000, position=3995]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 26873
+ sample count = 180
+ format 0:
+ averageBitrate = 112000
+ sampleMimeType = audio/vorbis
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 30, hash 9A8FF207
+ data = length 3832, hash 8A406249
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 49, hash 2FFF94F0
+ sample 1:
+ time = 0
+ flags = 1
+ data = length 44, hash 3946418A
+ sample 2:
+ time = 2666
+ flags = 1
+ data = length 55, hash 2A0B878E
+ sample 3:
+ time = 5333
+ flags = 1
+ data = length 53, hash CC3B6879
+ sample 4:
+ time = 8000
+ flags = 1
+ data = length 215, hash 106AE950
+ sample 5:
+ time = 20000
+ flags = 1
+ data = length 192, hash 2B219F53
+ sample 6:
+ time = 41333
+ flags = 1
+ data = length 197, hash FBC39422
+ sample 7:
+ time = 62666
+ flags = 1
+ data = length 209, hash 386E8979
+ sample 8:
+ time = 84000
+ flags = 1
+ data = length 42, hash E81162C1
+ sample 9:
+ time = 96000
+ flags = 1
+ data = length 41, hash F15BEE36
+ sample 10:
+ time = 98666
+ flags = 1
+ data = length 42, hash D67EB19
+ sample 11:
+ time = 101333
+ flags = 1
+ data = length 42, hash F4DE4792
+ sample 12:
+ time = 104000
+ flags = 1
+ data = length 53, hash 80F66AC3
+ sample 13:
+ time = 106666
+ flags = 1
+ data = length 56, hash DCB9DFC4
+ sample 14:
+ time = 109333
+ flags = 1
+ data = length 55, hash 4E0C4E9D
+ sample 15:
+ time = 112000
+ flags = 1
+ data = length 203, hash 176B6862
+ sample 16:
+ time = 124000
+ flags = 1
+ data = length 193, hash AB13CB10
+ sample 17:
+ time = 145333
+ flags = 1
+ data = length 203, hash DE63DE9F
+ sample 18:
+ time = 166666
+ flags = 1
+ data = length 194, hash 4A9508A2
+ sample 19:
+ time = 188000
+ flags = 1
+ data = length 210, hash 196899B3
+ sample 20:
+ time = 209333
+ flags = 1
+ data = length 195, hash B68407F1
+ sample 21:
+ time = 230666
+ flags = 1
+ data = length 193, hash A1FA86E3
+ sample 22:
+ time = 252000
+ flags = 1
+ data = length 194, hash 5C0B9343
+ sample 23:
+ time = 273333
+ flags = 1
+ data = length 198, hash 789914B2
+ sample 24:
+ time = 294666
+ flags = 1
+ data = length 183, hash 1B82D11F
+ sample 25:
+ time = 316000
+ flags = 1
+ data = length 199, hash D5B848F4
+ sample 26:
+ time = 337333
+ flags = 1
+ data = length 192, hash B34427EA
+ sample 27:
+ time = 358666
+ flags = 1
+ data = length 199, hash C2599BB5
+ sample 28:
+ time = 380000
+ flags = 1
+ data = length 195, hash BFD83194
+ sample 29:
+ time = 401333
+ flags = 1
+ data = length 199, hash C9A7F7CA
+ sample 30:
+ time = 422666
+ flags = 1
+ data = length 44, hash 5D76EAD6
+ sample 31:
+ time = 434666
+ flags = 1
+ data = length 43, hash 8619C423
+ sample 32:
+ time = 437333
+ flags = 1
+ data = length 43, hash E490BBE
+ sample 33:
+ time = 440000
+ flags = 1
+ data = length 53, hash 8A557CAE
+ sample 34:
+ time = 442666
+ flags = 1
+ data = length 56, hash 81007BBA
+ sample 35:
+ time = 445333
+ flags = 1
+ data = length 56, hash 4E4DD67F
+ sample 36:
+ time = 448000
+ flags = 1
+ data = length 222, hash 414188AB
+ sample 37:
+ time = 460000
+ flags = 1
+ data = length 202, hash 67A07D30
+ sample 38:
+ time = 481333
+ flags = 1
+ data = length 200, hash E357D853
+ sample 39:
+ time = 502666
+ flags = 1
+ data = length 203, hash 4653DC90
+ sample 40:
+ time = 524000
+ flags = 1
+ data = length 192, hash A65E6C09
+ sample 41:
+ time = 545333
+ flags = 1
+ data = length 202, hash FBEAC508
+ sample 42:
+ time = 566666
+ flags = 1
+ data = length 202, hash E9B7B59F
+ sample 43:
+ time = 588000
+ flags = 1
+ data = length 204, hash E24AA78E
+ sample 44:
+ time = 609333
+ flags = 1
+ data = length 41, hash 3FBC5216
+ sample 45:
+ time = 621333
+ flags = 1
+ data = length 47, hash 153FBC55
+ sample 46:
+ time = 624000
+ flags = 1
+ data = length 42, hash 2B493D6C
+ sample 47:
+ time = 626666
+ flags = 1
+ data = length 42, hash 8303BEE3
+ sample 48:
+ time = 629333
+ flags = 1
+ data = length 62, hash 71AEE50B
+ sample 49:
+ time = 632000
+ flags = 1
+ data = length 54, hash 52F61908
+ sample 50:
+ time = 634666
+ flags = 1
+ data = length 45, hash 7BD3E3A1
+ sample 51:
+ time = 637333
+ flags = 1
+ data = length 41, hash E0F65472
+ sample 52:
+ time = 640000
+ flags = 1
+ data = length 45, hash 41838675
+ sample 53:
+ time = 642666
+ flags = 1
+ data = length 44, hash FCBC2147
+ sample 54:
+ time = 645333
+ flags = 1
+ data = length 45, hash 1A5987E3
+ sample 55:
+ time = 648000
+ flags = 1
+ data = length 43, hash 99074864
+ sample 56:
+ time = 650666
+ flags = 1
+ data = length 57, hash D4A9B60A
+ sample 57:
+ time = 653333
+ flags = 1
+ data = length 52, hash 302129DA
+ sample 58:
+ time = 656000
+ flags = 1
+ data = length 57, hash D8DD99C0
+ sample 59:
+ time = 658666
+ flags = 1
+ data = length 206, hash F4B9EF26
+ sample 60:
+ time = 670666
+ flags = 1
+ data = length 197, hash 7B8ACC8A
+ sample 61:
+ time = 692000
+ flags = 1
+ data = length 186, hash 161027CB
+ sample 62:
+ time = 713333
+ flags = 1
+ data = length 186, hash 1D6871B6
+ sample 63:
+ time = 734666
+ flags = 1
+ data = length 201, hash 536E9FDB
+ sample 64:
+ time = 756000
+ flags = 1
+ data = length 192, hash D38EFAC5
+ sample 65:
+ time = 777333
+ flags = 1
+ data = length 194, hash 4B394EF3
+ sample 66:
+ time = 798666
+ flags = 1
+ data = length 206, hash 1B31BA99
+ sample 67:
+ time = 820000
+ flags = 1
+ data = length 212, hash AD061F43
+ sample 68:
+ time = 841333
+ flags = 1
+ data = length 180, hash 6D1F7481
+ sample 69:
+ time = 862666
+ flags = 1
+ data = length 195, hash D80B21F
+ sample 70:
+ time = 884000
+ flags = 1
+ data = length 186, hash D367882
+ sample 71:
+ time = 905333
+ flags = 1
+ data = length 195, hash 2722159A
+ sample 72:
+ time = 926666
+ flags = 1
+ data = length 199, hash 10CEE97A
+ sample 73:
+ time = 948000
+ flags = 1
+ data = length 191, hash 2CF9FB3F
+ sample 74:
+ time = 969333
+ flags = 1
+ data = length 197, hash A725DA0
+ sample 75:
+ time = 990666
+ flags = 1
+ data = length 211, hash D4E5DB9E
+ sample 76:
+ time = 1012000
+ flags = 1
+ data = length 189, hash 1A90F496
+ sample 77:
+ time = 1033333
+ flags = 1
+ data = length 187, hash 44DB2689
+ sample 78:
+ time = 1054666
+ flags = 1
+ data = length 197, hash 6D3E5117
+ sample 79:
+ time = 1076000
+ flags = 1
+ data = length 208, hash 5B57B288
+ sample 80:
+ time = 1097333
+ flags = 1
+ data = length 198, hash D5FC05
+ sample 81:
+ time = 1118666
+ flags = 1
+ data = length 192, hash 350BBA45
+ sample 82:
+ time = 1140000
+ flags = 1
+ data = length 195, hash 5F96F2A8
+ sample 83:
+ time = 1161333
+ flags = 1
+ data = length 202, hash 61D7CC33
+ sample 84:
+ time = 1182666
+ flags = 1
+ data = length 202, hash 49D335F2
+ sample 85:
+ time = 1204000
+ flags = 1
+ data = length 192, hash 2FE9CB1A
+ sample 86:
+ time = 1225333
+ flags = 1
+ data = length 201, hash BF0763B2
+ sample 87:
+ time = 1246666
+ flags = 1
+ data = length 184, hash AD047421
+ sample 88:
+ time = 1268000
+ flags = 1
+ data = length 196, hash F9088F14
+ sample 89:
+ time = 1289333
+ flags = 1
+ data = length 190, hash AC6D38FD
+ sample 90:
+ time = 1310666
+ flags = 1
+ data = length 195, hash 8D1A66D2
+ sample 91:
+ time = 1332000
+ flags = 1
+ data = length 197, hash B46BFB6B
+ sample 92:
+ time = 1353333
+ flags = 1
+ data = length 195, hash D9761F23
+ sample 93:
+ time = 1374666
+ flags = 1
+ data = length 204, hash 3391B617
+ sample 94:
+ time = 1396000
+ flags = 1
+ data = length 42, hash 33A1FB52
+ sample 95:
+ time = 1408000
+ flags = 1
+ data = length 44, hash 408B146E
+ sample 96:
+ time = 1410666
+ flags = 1
+ data = length 44, hash 171C7E0D
+ sample 97:
+ time = 1413333
+ flags = 1
+ data = length 54, hash 6307E16C
+ sample 98:
+ time = 1416000
+ flags = 1
+ data = length 53, hash 4A319572
+ sample 99:
+ time = 1418666
+ flags = 1
+ data = length 215, hash BA9C445C
+ sample 100:
+ time = 1430666
+ flags = 1
+ data = length 201, hash 3120D234
+ sample 101:
+ time = 1452000
+ flags = 1
+ data = length 187, hash DB44993C
+ sample 102:
+ time = 1473333
+ flags = 1
+ data = length 196, hash CF2002D7
+ sample 103:
+ time = 1494666
+ flags = 1
+ data = length 185, hash E03B5D7
+ sample 104:
+ time = 1516000
+ flags = 1
+ data = length 187, hash DA399A2C
+ sample 105:
+ time = 1537333
+ flags = 1
+ data = length 191, hash 292AA0DB
+ sample 106:
+ time = 1558666
+ flags = 1
+ data = length 201, hash 221910E0
+ sample 107:
+ time = 1580000
+ flags = 1
+ data = length 194, hash F4ED7821
+ sample 108:
+ time = 1601333
+ flags = 1
+ data = length 43, hash FDDA515E
+ sample 109:
+ time = 1613333
+ flags = 1
+ data = length 42, hash F3571C0A
+ sample 110:
+ time = 1616000
+ flags = 1
+ data = length 38, hash 39F910B3
+ sample 111:
+ time = 1618666
+ flags = 1
+ data = length 41, hash 2D189531
+ sample 112:
+ time = 1621333
+ flags = 1
+ data = length 43, hash 1F7574DB
+ sample 113:
+ time = 1624000
+ flags = 1
+ data = length 43, hash 644D15E5
+ sample 114:
+ time = 1626666
+ flags = 1
+ data = length 49, hash E8A0878
+ sample 115:
+ time = 1629333
+ flags = 1
+ data = length 55, hash DFF2046D
+ sample 116:
+ time = 1632000
+ flags = 1
+ data = length 49, hash 9FB8A23
+ sample 117:
+ time = 1634666
+ flags = 1
+ data = length 41, hash E3E15E3B
+ sample 118:
+ time = 1637333
+ flags = 1
+ data = length 42, hash E5D17A32
+ sample 119:
+ time = 1640000
+ flags = 1
+ data = length 42, hash F308B653
+ sample 120:
+ time = 1642666
+ flags = 1
+ data = length 55, hash BB750D76
+ sample 121:
+ time = 1645333
+ flags = 1
+ data = length 51, hash 96772ABF
+ sample 122:
+ time = 1648000
+ flags = 1
+ data = length 197, hash E4524346
+ sample 123:
+ time = 1660000
+ flags = 1
+ data = length 188, hash AC3E1BB5
+ sample 124:
+ time = 1681333
+ flags = 1
+ data = length 195, hash F56DB8A5
+ sample 125:
+ time = 1702666
+ flags = 1
+ data = length 198, hash C8970FF7
+ sample 126:
+ time = 1724000
+ flags = 1
+ data = length 202, hash AF425C68
+ sample 127:
+ time = 1745333
+ flags = 1
+ data = length 196, hash 4215D839
+ sample 128:
+ time = 1766666
+ flags = 1
+ data = length 204, hash DB9BE8E3
+ sample 129:
+ time = 1788000
+ flags = 1
+ data = length 206, hash E5B20AB8
+ sample 130:
+ time = 1809333
+ flags = 1
+ data = length 209, hash D7F47B95
+ sample 131:
+ time = 1830666
+ flags = 1
+ data = length 193, hash FB54FB05
+ sample 132:
+ time = 1852000
+ flags = 1
+ data = length 199, hash D99C3106
+ sample 133:
+ time = 1873333
+ flags = 1
+ data = length 206, hash 253885B9
+ sample 134:
+ time = 1894666
+ flags = 1
+ data = length 191, hash FBDD8162
+ sample 135:
+ time = 1916000
+ flags = 1
+ data = length 183, hash 7290332F
+ sample 136:
+ time = 1937333
+ flags = 1
+ data = length 189, hash 1A9DC3DE
+ sample 137:
+ time = 1958666
+ flags = 1
+ data = length 201, hash 5D936764
+ sample 138:
+ time = 1980000
+ flags = 1
+ data = length 193, hash 6B03E75E
+ sample 139:
+ time = 2001333
+ flags = 1
+ data = length 199, hash 8A21BA83
+ sample 140:
+ time = 2022666
+ flags = 1
+ data = length 41, hash E6362210
+ sample 141:
+ time = 2034666
+ flags = 1
+ data = length 43, hash 36A57B44
+ sample 142:
+ time = 2037333
+ flags = 1
+ data = length 43, hash E51797D5
+ sample 143:
+ time = 2040000
+ flags = 1
+ data = length 43, hash 1F336C72
+ sample 144:
+ time = 2042666
+ flags = 1
+ data = length 42, hash 201AD367
+ sample 145:
+ time = 2045333
+ flags = 1
+ data = length 50, hash 606CCD6
+ sample 146:
+ time = 2048000
+ flags = 1
+ data = length 56, hash B15EBD7A
+ sample 147:
+ time = 2050666
+ flags = 1
+ data = length 212, hash 273B8D22
+ sample 148:
+ time = 2062666
+ flags = 1
+ data = length 194, hash 44F9CE1
+ sample 149:
+ time = 2084000
+ flags = 1
+ data = length 195, hash EDF9EBA1
+ sample 150:
+ time = 2105333
+ flags = 1
+ data = length 194, hash CE9F2D26
+ sample 151:
+ time = 2126666
+ flags = 1
+ data = length 192, hash 204F8A23
+ sample 152:
+ time = 2148000
+ flags = 1
+ data = length 206, hash DFA57E67
+ sample 153:
+ time = 2169333
+ flags = 1
+ data = length 196, hash 3CF084AB
+ sample 154:
+ time = 2190666
+ flags = 1
+ data = length 202, hash 2AF75C08
+ sample 155:
+ time = 2212000
+ flags = 1
+ data = length 203, hash 748EAF7
+ sample 156:
+ time = 2233333
+ flags = 1
+ data = length 205, hash ED82379D
+ sample 157:
+ time = 2254666
+ flags = 1
+ data = length 193, hash 61F26F22
+ sample 158:
+ time = 2276000
+ flags = 1
+ data = length 189, hash 85EF1D20
+ sample 159:
+ time = 2297333
+ flags = 1
+ data = length 187, hash 25E41FBF
+ sample 160:
+ time = 2318666
+ flags = 1
+ data = length 199, hash F365808
+ sample 161:
+ time = 2340000
+ flags = 1
+ data = length 197, hash 94205329
+ sample 162:
+ time = 2361333
+ flags = 1
+ data = length 201, hash FA2B2055
+ sample 163:
+ time = 2382666
+ flags = 1
+ data = length 194, hash AF95381F
+ sample 164:
+ time = 2404000
+ flags = 1
+ data = length 201, hash 923D3534
+ sample 165:
+ time = 2425333
+ flags = 1
+ data = length 198, hash 35F84C2E
+ sample 166:
+ time = 2446666
+ flags = 1
+ data = length 204, hash 6642CA40
+ sample 167:
+ time = 2468000
+ flags = 1
+ data = length 183, hash 3E2DC6BE
+ sample 168:
+ time = 2489333
+ flags = 1
+ data = length 197, hash B1E458CE
+ sample 169:
+ time = 2510666
+ flags = 1
+ data = length 193, hash E9218C84
+ sample 170:
+ time = 2532000
+ flags = 1
+ data = length 192, hash FEF08D4B
+ sample 171:
+ time = 2553333
+ flags = 1
+ data = length 201, hash FC411147
+ sample 172:
+ time = 2574666
+ flags = 1
+ data = length 218, hash 86893464
+ sample 173:
+ time = 2596000
+ flags = 1
+ data = length 226, hash 31C5320
+ sample 174:
+ time = 2617333
+ flags = 1
+ data = length 233, hash 9432BEE5
+ sample 175:
+ time = 2638666
+ flags = 1
+ data = length 213, hash B3FCC53E
+ sample 176:
+ time = 2660000
+ flags = 1
+ data = length 204, hash D70DD5A2
+ sample 177:
+ time = 2681333
+ flags = 1
+ data = length 212, hash A4EF1B69
+ sample 178:
+ time = 2702666
+ flags = 1
+ data = length 203, hash 8B0748B5
+ sample 179:
+ time = 2724000
+ flags = 1
+ data = length 149, hash E455335B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.1.dump b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.1.dump
new file mode 100644
index 0000000..2faeecb
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.1.dump
@@ -0,0 +1,456 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=3995]]
+ getPosition(1) = [[timeUs=1, position=3995]]
+ getPosition(1370500) = [[timeUs=1370500, position=3995]]
+ getPosition(2741000) = [[timeUs=2741000, position=3995]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 17598
+ sample count = 109
+ format 0:
+ averageBitrate = 112000
+ sampleMimeType = audio/vorbis
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 30, hash 9A8FF207
+ data = length 3832, hash 8A406249
+ sample 0:
+ time = 896000
+ flags = 1
+ data = length 195, hash 2722159A
+ sample 1:
+ time = 917333
+ flags = 1
+ data = length 199, hash 10CEE97A
+ sample 2:
+ time = 938666
+ flags = 1
+ data = length 191, hash 2CF9FB3F
+ sample 3:
+ time = 960000
+ flags = 1
+ data = length 197, hash A725DA0
+ sample 4:
+ time = 981333
+ flags = 1
+ data = length 211, hash D4E5DB9E
+ sample 5:
+ time = 1002666
+ flags = 1
+ data = length 189, hash 1A90F496
+ sample 6:
+ time = 1024000
+ flags = 1
+ data = length 187, hash 44DB2689
+ sample 7:
+ time = 1045333
+ flags = 1
+ data = length 197, hash 6D3E5117
+ sample 8:
+ time = 1066666
+ flags = 1
+ data = length 208, hash 5B57B288
+ sample 9:
+ time = 1088000
+ flags = 1
+ data = length 198, hash D5FC05
+ sample 10:
+ time = 1109333
+ flags = 1
+ data = length 192, hash 350BBA45
+ sample 11:
+ time = 1130666
+ flags = 1
+ data = length 195, hash 5F96F2A8
+ sample 12:
+ time = 1152000
+ flags = 1
+ data = length 202, hash 61D7CC33
+ sample 13:
+ time = 1173333
+ flags = 1
+ data = length 202, hash 49D335F2
+ sample 14:
+ time = 1194666
+ flags = 1
+ data = length 192, hash 2FE9CB1A
+ sample 15:
+ time = 1216000
+ flags = 1
+ data = length 201, hash BF0763B2
+ sample 16:
+ time = 1237333
+ flags = 1
+ data = length 184, hash AD047421
+ sample 17:
+ time = 1258666
+ flags = 1
+ data = length 196, hash F9088F14
+ sample 18:
+ time = 1280000
+ flags = 1
+ data = length 190, hash AC6D38FD
+ sample 19:
+ time = 1301333
+ flags = 1
+ data = length 195, hash 8D1A66D2
+ sample 20:
+ time = 1322666
+ flags = 1
+ data = length 197, hash B46BFB6B
+ sample 21:
+ time = 1344000
+ flags = 1
+ data = length 195, hash D9761F23
+ sample 22:
+ time = 1365333
+ flags = 1
+ data = length 204, hash 3391B617
+ sample 23:
+ time = 1386666
+ flags = 1
+ data = length 42, hash 33A1FB52
+ sample 24:
+ time = 1398666
+ flags = 1
+ data = length 44, hash 408B146E
+ sample 25:
+ time = 1401333
+ flags = 1
+ data = length 44, hash 171C7E0D
+ sample 26:
+ time = 1404000
+ flags = 1
+ data = length 54, hash 6307E16C
+ sample 27:
+ time = 1406666
+ flags = 1
+ data = length 53, hash 4A319572
+ sample 28:
+ time = 1409333
+ flags = 1
+ data = length 215, hash BA9C445C
+ sample 29:
+ time = 1421333
+ flags = 1
+ data = length 201, hash 3120D234
+ sample 30:
+ time = 1442666
+ flags = 1
+ data = length 187, hash DB44993C
+ sample 31:
+ time = 1464000
+ flags = 1
+ data = length 196, hash CF2002D7
+ sample 32:
+ time = 1485333
+ flags = 1
+ data = length 185, hash E03B5D7
+ sample 33:
+ time = 1506666
+ flags = 1
+ data = length 187, hash DA399A2C
+ sample 34:
+ time = 1528000
+ flags = 1
+ data = length 191, hash 292AA0DB
+ sample 35:
+ time = 1549333
+ flags = 1
+ data = length 201, hash 221910E0
+ sample 36:
+ time = 1570666
+ flags = 1
+ data = length 194, hash F4ED7821
+ sample 37:
+ time = 1592000
+ flags = 1
+ data = length 43, hash FDDA515E
+ sample 38:
+ time = 1604000
+ flags = 1
+ data = length 42, hash F3571C0A
+ sample 39:
+ time = 1606666
+ flags = 1
+ data = length 38, hash 39F910B3
+ sample 40:
+ time = 1609333
+ flags = 1
+ data = length 41, hash 2D189531
+ sample 41:
+ time = 1612000
+ flags = 1
+ data = length 43, hash 1F7574DB
+ sample 42:
+ time = 1614666
+ flags = 1
+ data = length 43, hash 644D15E5
+ sample 43:
+ time = 1617333
+ flags = 1
+ data = length 49, hash E8A0878
+ sample 44:
+ time = 1620000
+ flags = 1
+ data = length 55, hash DFF2046D
+ sample 45:
+ time = 1622666
+ flags = 1
+ data = length 49, hash 9FB8A23
+ sample 46:
+ time = 1625333
+ flags = 1
+ data = length 41, hash E3E15E3B
+ sample 47:
+ time = 1628000
+ flags = 1
+ data = length 42, hash E5D17A32
+ sample 48:
+ time = 1630666
+ flags = 1
+ data = length 42, hash F308B653
+ sample 49:
+ time = 1633333
+ flags = 1
+ data = length 55, hash BB750D76
+ sample 50:
+ time = 1636000
+ flags = 1
+ data = length 51, hash 96772ABF
+ sample 51:
+ time = 1638666
+ flags = 1
+ data = length 197, hash E4524346
+ sample 52:
+ time = 1650666
+ flags = 1
+ data = length 188, hash AC3E1BB5
+ sample 53:
+ time = 1672000
+ flags = 1
+ data = length 195, hash F56DB8A5
+ sample 54:
+ time = 1693333
+ flags = 1
+ data = length 198, hash C8970FF7
+ sample 55:
+ time = 1714666
+ flags = 1
+ data = length 202, hash AF425C68
+ sample 56:
+ time = 1736000
+ flags = 1
+ data = length 196, hash 4215D839
+ sample 57:
+ time = 1757333
+ flags = 1
+ data = length 204, hash DB9BE8E3
+ sample 58:
+ time = 1778666
+ flags = 1
+ data = length 206, hash E5B20AB8
+ sample 59:
+ time = 1800000
+ flags = 1
+ data = length 209, hash D7F47B95
+ sample 60:
+ time = 1821333
+ flags = 1
+ data = length 193, hash FB54FB05
+ sample 61:
+ time = 1842666
+ flags = 1
+ data = length 199, hash D99C3106
+ sample 62:
+ time = 1864000
+ flags = 1
+ data = length 206, hash 253885B9
+ sample 63:
+ time = 1885333
+ flags = 1
+ data = length 191, hash FBDD8162
+ sample 64:
+ time = 1906666
+ flags = 1
+ data = length 183, hash 7290332F
+ sample 65:
+ time = 1928000
+ flags = 1
+ data = length 189, hash 1A9DC3DE
+ sample 66:
+ time = 1949333
+ flags = 1
+ data = length 201, hash 5D936764
+ sample 67:
+ time = 1970666
+ flags = 1
+ data = length 193, hash 6B03E75E
+ sample 68:
+ time = 1992000
+ flags = 1
+ data = length 199, hash 8A21BA83
+ sample 69:
+ time = 2013333
+ flags = 1
+ data = length 41, hash E6362210
+ sample 70:
+ time = 2025333
+ flags = 1
+ data = length 43, hash 36A57B44
+ sample 71:
+ time = 2028000
+ flags = 1
+ data = length 43, hash E51797D5
+ sample 72:
+ time = 2030666
+ flags = 1
+ data = length 43, hash 1F336C72
+ sample 73:
+ time = 2033333
+ flags = 1
+ data = length 42, hash 201AD367
+ sample 74:
+ time = 2036000
+ flags = 1
+ data = length 50, hash 606CCD6
+ sample 75:
+ time = 2038666
+ flags = 1
+ data = length 56, hash B15EBD7A
+ sample 76:
+ time = 2041333
+ flags = 1
+ data = length 212, hash 273B8D22
+ sample 77:
+ time = 2053333
+ flags = 1
+ data = length 194, hash 44F9CE1
+ sample 78:
+ time = 2074666
+ flags = 1
+ data = length 195, hash EDF9EBA1
+ sample 79:
+ time = 2096000
+ flags = 1
+ data = length 194, hash CE9F2D26
+ sample 80:
+ time = 2117333
+ flags = 1
+ data = length 192, hash 204F8A23
+ sample 81:
+ time = 2138666
+ flags = 1
+ data = length 206, hash DFA57E67
+ sample 82:
+ time = 2160000
+ flags = 1
+ data = length 196, hash 3CF084AB
+ sample 83:
+ time = 2181333
+ flags = 1
+ data = length 202, hash 2AF75C08
+ sample 84:
+ time = 2202666
+ flags = 1
+ data = length 203, hash 748EAF7
+ sample 85:
+ time = 2224000
+ flags = 1
+ data = length 205, hash ED82379D
+ sample 86:
+ time = 2245333
+ flags = 1
+ data = length 193, hash 61F26F22
+ sample 87:
+ time = 2266666
+ flags = 1
+ data = length 189, hash 85EF1D20
+ sample 88:
+ time = 2288000
+ flags = 1
+ data = length 187, hash 25E41FBF
+ sample 89:
+ time = 2309333
+ flags = 1
+ data = length 199, hash F365808
+ sample 90:
+ time = 2330666
+ flags = 1
+ data = length 197, hash 94205329
+ sample 91:
+ time = 2352000
+ flags = 1
+ data = length 201, hash FA2B2055
+ sample 92:
+ time = 2373333
+ flags = 1
+ data = length 194, hash AF95381F
+ sample 93:
+ time = 2394666
+ flags = 1
+ data = length 201, hash 923D3534
+ sample 94:
+ time = 2416000
+ flags = 1
+ data = length 198, hash 35F84C2E
+ sample 95:
+ time = 2437333
+ flags = 1
+ data = length 204, hash 6642CA40
+ sample 96:
+ time = 2458666
+ flags = 1
+ data = length 183, hash 3E2DC6BE
+ sample 97:
+ time = 2480000
+ flags = 1
+ data = length 197, hash B1E458CE
+ sample 98:
+ time = 2501333
+ flags = 1
+ data = length 193, hash E9218C84
+ sample 99:
+ time = 2522666
+ flags = 1
+ data = length 192, hash FEF08D4B
+ sample 100:
+ time = 2544000
+ flags = 1
+ data = length 201, hash FC411147
+ sample 101:
+ time = 2565333
+ flags = 1
+ data = length 218, hash 86893464
+ sample 102:
+ time = 2586666
+ flags = 1
+ data = length 226, hash 31C5320
+ sample 103:
+ time = 2608000
+ flags = 1
+ data = length 233, hash 9432BEE5
+ sample 104:
+ time = 2629333
+ flags = 1
+ data = length 213, hash B3FCC53E
+ sample 105:
+ time = 2650666
+ flags = 1
+ data = length 204, hash D70DD5A2
+ sample 106:
+ time = 2672000
+ flags = 1
+ data = length 212, hash A4EF1B69
+ sample 107:
+ time = 2693333
+ flags = 1
+ data = length 203, hash 8B0748B5
+ sample 108:
+ time = 2714666
+ flags = 1
+ data = length 149, hash E455335B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.2.dump b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.2.dump
new file mode 100644
index 0000000..3536830
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.2.dump
@@ -0,0 +1,216 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=3995]]
+ getPosition(1) = [[timeUs=1, position=3995]]
+ getPosition(1370500) = [[timeUs=1370500, position=3995]]
+ getPosition(2741000) = [[timeUs=2741000, position=3995]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 8658
+ sample count = 49
+ format 0:
+ averageBitrate = 112000
+ sampleMimeType = audio/vorbis
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 30, hash 9A8FF207
+ data = length 3832, hash 8A406249
+ sample 0:
+ time = 1821333
+ flags = 1
+ data = length 193, hash FB54FB05
+ sample 1:
+ time = 1842666
+ flags = 1
+ data = length 199, hash D99C3106
+ sample 2:
+ time = 1864000
+ flags = 1
+ data = length 206, hash 253885B9
+ sample 3:
+ time = 1885333
+ flags = 1
+ data = length 191, hash FBDD8162
+ sample 4:
+ time = 1906666
+ flags = 1
+ data = length 183, hash 7290332F
+ sample 5:
+ time = 1928000
+ flags = 1
+ data = length 189, hash 1A9DC3DE
+ sample 6:
+ time = 1949333
+ flags = 1
+ data = length 201, hash 5D936764
+ sample 7:
+ time = 1970666
+ flags = 1
+ data = length 193, hash 6B03E75E
+ sample 8:
+ time = 1992000
+ flags = 1
+ data = length 199, hash 8A21BA83
+ sample 9:
+ time = 2013333
+ flags = 1
+ data = length 41, hash E6362210
+ sample 10:
+ time = 2025333
+ flags = 1
+ data = length 43, hash 36A57B44
+ sample 11:
+ time = 2028000
+ flags = 1
+ data = length 43, hash E51797D5
+ sample 12:
+ time = 2030666
+ flags = 1
+ data = length 43, hash 1F336C72
+ sample 13:
+ time = 2033333
+ flags = 1
+ data = length 42, hash 201AD367
+ sample 14:
+ time = 2036000
+ flags = 1
+ data = length 50, hash 606CCD6
+ sample 15:
+ time = 2038666
+ flags = 1
+ data = length 56, hash B15EBD7A
+ sample 16:
+ time = 2041333
+ flags = 1
+ data = length 212, hash 273B8D22
+ sample 17:
+ time = 2053333
+ flags = 1
+ data = length 194, hash 44F9CE1
+ sample 18:
+ time = 2074666
+ flags = 1
+ data = length 195, hash EDF9EBA1
+ sample 19:
+ time = 2096000
+ flags = 1
+ data = length 194, hash CE9F2D26
+ sample 20:
+ time = 2117333
+ flags = 1
+ data = length 192, hash 204F8A23
+ sample 21:
+ time = 2138666
+ flags = 1
+ data = length 206, hash DFA57E67
+ sample 22:
+ time = 2160000
+ flags = 1
+ data = length 196, hash 3CF084AB
+ sample 23:
+ time = 2181333
+ flags = 1
+ data = length 202, hash 2AF75C08
+ sample 24:
+ time = 2202666
+ flags = 1
+ data = length 203, hash 748EAF7
+ sample 25:
+ time = 2224000
+ flags = 1
+ data = length 205, hash ED82379D
+ sample 26:
+ time = 2245333
+ flags = 1
+ data = length 193, hash 61F26F22
+ sample 27:
+ time = 2266666
+ flags = 1
+ data = length 189, hash 85EF1D20
+ sample 28:
+ time = 2288000
+ flags = 1
+ data = length 187, hash 25E41FBF
+ sample 29:
+ time = 2309333
+ flags = 1
+ data = length 199, hash F365808
+ sample 30:
+ time = 2330666
+ flags = 1
+ data = length 197, hash 94205329
+ sample 31:
+ time = 2352000
+ flags = 1
+ data = length 201, hash FA2B2055
+ sample 32:
+ time = 2373333
+ flags = 1
+ data = length 194, hash AF95381F
+ sample 33:
+ time = 2394666
+ flags = 1
+ data = length 201, hash 923D3534
+ sample 34:
+ time = 2416000
+ flags = 1
+ data = length 198, hash 35F84C2E
+ sample 35:
+ time = 2437333
+ flags = 1
+ data = length 204, hash 6642CA40
+ sample 36:
+ time = 2458666
+ flags = 1
+ data = length 183, hash 3E2DC6BE
+ sample 37:
+ time = 2480000
+ flags = 1
+ data = length 197, hash B1E458CE
+ sample 38:
+ time = 2501333
+ flags = 1
+ data = length 193, hash E9218C84
+ sample 39:
+ time = 2522666
+ flags = 1
+ data = length 192, hash FEF08D4B
+ sample 40:
+ time = 2544000
+ flags = 1
+ data = length 201, hash FC411147
+ sample 41:
+ time = 2565333
+ flags = 1
+ data = length 218, hash 86893464
+ sample 42:
+ time = 2586666
+ flags = 1
+ data = length 226, hash 31C5320
+ sample 43:
+ time = 2608000
+ flags = 1
+ data = length 233, hash 9432BEE5
+ sample 44:
+ time = 2629333
+ flags = 1
+ data = length 213, hash B3FCC53E
+ sample 45:
+ time = 2650666
+ flags = 1
+ data = length 204, hash D70DD5A2
+ sample 46:
+ time = 2672000
+ flags = 1
+ data = length 212, hash A4EF1B69
+ sample 47:
+ time = 2693333
+ flags = 1
+ data = length 203, hash 8B0748B5
+ sample 48:
+ time = 2714666
+ flags = 1
+ data = length 149, hash E455335B
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.3.dump b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.3.dump
new file mode 100644
index 0000000..1975852
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.3.dump
@@ -0,0 +1,20 @@
+seekMap:
+ isSeekable = true
+ duration = 2741000
+ getPosition(0) = [[timeUs=0, position=3995]]
+ getPosition(1) = [[timeUs=1, position=3995]]
+ getPosition(1370500) = [[timeUs=1370500, position=3995]]
+ getPosition(2741000) = [[timeUs=2741000, position=3995]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ averageBitrate = 112000
+ sampleMimeType = audio/vorbis
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 30, hash 9A8FF207
+ data = length 3832, hash 8A406249
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.unknown_length.dump b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.unknown_length.dump
new file mode 100644
index 0000000..9830a08
--- /dev/null
+++ b/tree/testdata/src/test/assets/ogg/bear_vorbis.ogg.unknown_length.dump
@@ -0,0 +1,737 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 26873
+ sample count = 180
+ format 0:
+ averageBitrate = 112000
+ sampleMimeType = audio/vorbis
+ channelCount = 2
+ sampleRate = 48000
+ initializationData:
+ data = length 30, hash 9A8FF207
+ data = length 3832, hash 8A406249
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 49, hash 2FFF94F0
+ sample 1:
+ time = 0
+ flags = 1
+ data = length 44, hash 3946418A
+ sample 2:
+ time = 2666
+ flags = 1
+ data = length 55, hash 2A0B878E
+ sample 3:
+ time = 5333
+ flags = 1
+ data = length 53, hash CC3B6879
+ sample 4:
+ time = 8000
+ flags = 1
+ data = length 215, hash 106AE950
+ sample 5:
+ time = 20000
+ flags = 1
+ data = length 192, hash 2B219F53
+ sample 6:
+ time = 41333
+ flags = 1
+ data = length 197, hash FBC39422
+ sample 7:
+ time = 62666
+ flags = 1
+ data = length 209, hash 386E8979
+ sample 8:
+ time = 84000
+ flags = 1
+ data = length 42, hash E81162C1
+ sample 9:
+ time = 96000
+ flags = 1
+ data = length 41, hash F15BEE36
+ sample 10:
+ time = 98666
+ flags = 1
+ data = length 42, hash D67EB19
+ sample 11:
+ time = 101333
+ flags = 1
+ data = length 42, hash F4DE4792
+ sample 12:
+ time = 104000
+ flags = 1
+ data = length 53, hash 80F66AC3
+ sample 13:
+ time = 106666
+ flags = 1
+ data = length 56, hash DCB9DFC4
+ sample 14:
+ time = 109333
+ flags = 1
+ data = length 55, hash 4E0C4E9D
+ sample 15:
+ time = 112000
+ flags = 1
+ data = length 203, hash 176B6862
+ sample 16:
+ time = 124000
+ flags = 1
+ data = length 193, hash AB13CB10
+ sample 17:
+ time = 145333
+ flags = 1
+ data = length 203, hash DE63DE9F
+ sample 18:
+ time = 166666
+ flags = 1
+ data = length 194, hash 4A9508A2
+ sample 19:
+ time = 188000
+ flags = 1
+ data = length 210, hash 196899B3
+ sample 20:
+ time = 209333
+ flags = 1
+ data = length 195, hash B68407F1
+ sample 21:
+ time = 230666
+ flags = 1
+ data = length 193, hash A1FA86E3
+ sample 22:
+ time = 252000
+ flags = 1
+ data = length 194, hash 5C0B9343
+ sample 23:
+ time = 273333
+ flags = 1
+ data = length 198, hash 789914B2
+ sample 24:
+ time = 294666
+ flags = 1
+ data = length 183, hash 1B82D11F
+ sample 25:
+ time = 316000
+ flags = 1
+ data = length 199, hash D5B848F4
+ sample 26:
+ time = 337333
+ flags = 1
+ data = length 192, hash B34427EA
+ sample 27:
+ time = 358666
+ flags = 1
+ data = length 199, hash C2599BB5
+ sample 28:
+ time = 380000
+ flags = 1
+ data = length 195, hash BFD83194
+ sample 29:
+ time = 401333
+ flags = 1
+ data = length 199, hash C9A7F7CA
+ sample 30:
+ time = 422666
+ flags = 1
+ data = length 44, hash 5D76EAD6
+ sample 31:
+ time = 434666
+ flags = 1
+ data = length 43, hash 8619C423
+ sample 32:
+ time = 437333
+ flags = 1
+ data = length 43, hash E490BBE
+ sample 33:
+ time = 440000
+ flags = 1
+ data = length 53, hash 8A557CAE
+ sample 34:
+ time = 442666
+ flags = 1
+ data = length 56, hash 81007BBA
+ sample 35:
+ time = 445333
+ flags = 1
+ data = length 56, hash 4E4DD67F
+ sample 36:
+ time = 448000
+ flags = 1
+ data = length 222, hash 414188AB
+ sample 37:
+ time = 460000
+ flags = 1
+ data = length 202, hash 67A07D30
+ sample 38:
+ time = 481333
+ flags = 1
+ data = length 200, hash E357D853
+ sample 39:
+ time = 502666
+ flags = 1
+ data = length 203, hash 4653DC90
+ sample 40:
+ time = 524000
+ flags = 1
+ data = length 192, hash A65E6C09
+ sample 41:
+ time = 545333
+ flags = 1
+ data = length 202, hash FBEAC508
+ sample 42:
+ time = 566666
+ flags = 1
+ data = length 202, hash E9B7B59F
+ sample 43:
+ time = 588000
+ flags = 1
+ data = length 204, hash E24AA78E
+ sample 44:
+ time = 609333
+ flags = 1
+ data = length 41, hash 3FBC5216
+ sample 45:
+ time = 621333
+ flags = 1
+ data = length 47, hash 153FBC55
+ sample 46:
+ time = 624000
+ flags = 1
+ data = length 42, hash 2B493D6C
+ sample 47:
+ time = 626666
+ flags = 1
+ data = length 42, hash 8303BEE3
+ sample 48:
+ time = 629333
+ flags = 1
+ data = length 62, hash 71AEE50B
+ sample 49:
+ time = 632000
+ flags = 1
+ data = length 54, hash 52F61908
+ sample 50:
+ time = 634666
+ flags = 1
+ data = length 45, hash 7BD3E3A1
+ sample 51:
+ time = 637333
+ flags = 1
+ data = length 41, hash E0F65472
+ sample 52:
+ time = 640000
+ flags = 1
+ data = length 45, hash 41838675
+ sample 53:
+ time = 642666
+ flags = 1
+ data = length 44, hash FCBC2147
+ sample 54:
+ time = 645333
+ flags = 1
+ data = length 45, hash 1A5987E3
+ sample 55:
+ time = 648000
+ flags = 1
+ data = length 43, hash 99074864
+ sample 56:
+ time = 650666
+ flags = 1
+ data = length 57, hash D4A9B60A
+ sample 57:
+ time = 653333
+ flags = 1
+ data = length 52, hash 302129DA
+ sample 58:
+ time = 656000
+ flags = 1
+ data = length 57, hash D8DD99C0
+ sample 59:
+ time = 658666
+ flags = 1
+ data = length 206, hash F4B9EF26
+ sample 60:
+ time = 670666
+ flags = 1
+ data = length 197, hash 7B8ACC8A
+ sample 61:
+ time = 692000
+ flags = 1
+ data = length 186, hash 161027CB
+ sample 62:
+ time = 713333
+ flags = 1
+ data = length 186, hash 1D6871B6
+ sample 63:
+ time = 734666
+ flags = 1
+ data = length 201, hash 536E9FDB
+ sample 64:
+ time = 756000
+ flags = 1
+ data = length 192, hash D38EFAC5
+ sample 65:
+ time = 777333
+ flags = 1
+ data = length 194, hash 4B394EF3
+ sample 66:
+ time = 798666
+ flags = 1
+ data = length 206, hash 1B31BA99
+ sample 67:
+ time = 820000
+ flags = 1
+ data = length 212, hash AD061F43
+ sample 68:
+ time = 841333
+ flags = 1
+ data = length 180, hash 6D1F7481
+ sample 69:
+ time = 862666
+ flags = 1
+ data = length 195, hash D80B21F
+ sample 70:
+ time = 884000
+ flags = 1
+ data = length 186, hash D367882
+ sample 71:
+ time = 905333
+ flags = 1
+ data = length 195, hash 2722159A
+ sample 72:
+ time = 926666
+ flags = 1
+ data = length 199, hash 10CEE97A
+ sample 73:
+ time = 948000
+ flags = 1
+ data = length 191, hash 2CF9FB3F
+ sample 74:
+ time = 969333
+ flags = 1
+ data = length 197, hash A725DA0
+ sample 75:
+ time = 990666
+ flags = 1
+ data = length 211, hash D4E5DB9E
+ sample 76:
+ time = 1012000
+ flags = 1
+ data = length 189, hash 1A90F496
+ sample 77:
+ time = 1033333
+ flags = 1
+ data = length 187, hash 44DB2689
+ sample 78:
+ time = 1054666
+ flags = 1
+ data = length 197, hash 6D3E5117
+ sample 79:
+ time = 1076000
+ flags = 1
+ data = length 208, hash 5B57B288
+ sample 80:
+ time = 1097333
+ flags = 1
+ data = length 198, hash D5FC05
+ sample 81:
+ time = 1118666
+ flags = 1
+ data = length 192, hash 350BBA45
+ sample 82:
+ time = 1140000
+ flags = 1
+ data = length 195, hash 5F96F2A8
+ sample 83:
+ time = 1161333
+ flags = 1
+ data = length 202, hash 61D7CC33
+ sample 84:
+ time = 1182666
+ flags = 1
+ data = length 202, hash 49D335F2
+ sample 85:
+ time = 1204000
+ flags = 1
+ data = length 192, hash 2FE9CB1A
+ sample 86:
+ time = 1225333
+ flags = 1
+ data = length 201, hash BF0763B2
+ sample 87:
+ time = 1246666
+ flags = 1
+ data = length 184, hash AD047421
+ sample 88:
+ time = 1268000
+ flags = 1
+ data = length 196, hash F9088F14
+ sample 89:
+ time = 1289333
+ flags = 1
+ data = length 190, hash AC6D38FD
+ sample 90:
+ time = 1310666
+ flags = 1
+ data = length 195, hash 8D1A66D2
+ sample 91:
+ time = 1332000
+ flags = 1
+ data = length 197, hash B46BFB6B
+ sample 92:
+ time = 1353333
+ flags = 1
+ data = length 195, hash D9761F23
+ sample 93:
+ time = 1374666
+ flags = 1
+ data = length 204, hash 3391B617
+ sample 94:
+ time = 1396000
+ flags = 1
+ data = length 42, hash 33A1FB52
+ sample 95:
+ time = 1408000
+ flags = 1
+ data = length 44, hash 408B146E
+ sample 96:
+ time = 1410666
+ flags = 1
+ data = length 44, hash 171C7E0D
+ sample 97:
+ time = 1413333
+ flags = 1
+ data = length 54, hash 6307E16C
+ sample 98:
+ time = 1416000
+ flags = 1
+ data = length 53, hash 4A319572
+ sample 99:
+ time = 1418666
+ flags = 1
+ data = length 215, hash BA9C445C
+ sample 100:
+ time = 1430666
+ flags = 1
+ data = length 201, hash 3120D234
+ sample 101:
+ time = 1452000
+ flags = 1
+ data = length 187, hash DB44993C
+ sample 102:
+ time = 1473333
+ flags = 1
+ data = length 196, hash CF2002D7
+ sample 103:
+ time = 1494666
+ flags = 1
+ data = length 185, hash E03B5D7
+ sample 104:
+ time = 1516000
+ flags = 1
+ data = length 187, hash DA399A2C
+ sample 105:
+ time = 1537333
+ flags = 1
+ data = length 191, hash 292AA0DB
+ sample 106:
+ time = 1558666
+ flags = 1
+ data = length 201, hash 221910E0
+ sample 107:
+ time = 1580000
+ flags = 1
+ data = length 194, hash F4ED7821
+ sample 108:
+ time = 1601333
+ flags = 1
+ data = length 43, hash FDDA515E
+ sample 109:
+ time = 1613333
+ flags = 1
+ data = length 42, hash F3571C0A
+ sample 110:
+ time = 1616000
+ flags = 1
+ data = length 38, hash 39F910B3
+ sample 111:
+ time = 1618666
+ flags = 1
+ data = length 41, hash 2D189531
+ sample 112:
+ time = 1621333
+ flags = 1
+ data = length 43, hash 1F7574DB
+ sample 113:
+ time = 1624000
+ flags = 1
+ data = length 43, hash 644D15E5
+ sample 114:
+ time = 1626666
+ flags = 1
+ data = length 49, hash E8A0878
+ sample 115:
+ time = 1629333
+ flags = 1
+ data = length 55, hash DFF2046D
+ sample 116:
+ time = 1632000
+ flags = 1
+ data = length 49, hash 9FB8A23
+ sample 117:
+ time = 1634666
+ flags = 1
+ data = length 41, hash E3E15E3B
+ sample 118:
+ time = 1637333
+ flags = 1
+ data = length 42, hash E5D17A32
+ sample 119:
+ time = 1640000
+ flags = 1
+ data = length 42, hash F308B653
+ sample 120:
+ time = 1642666
+ flags = 1
+ data = length 55, hash BB750D76
+ sample 121:
+ time = 1645333
+ flags = 1
+ data = length 51, hash 96772ABF
+ sample 122:
+ time = 1648000
+ flags = 1
+ data = length 197, hash E4524346
+ sample 123:
+ time = 1660000
+ flags = 1
+ data = length 188, hash AC3E1BB5
+ sample 124:
+ time = 1681333
+ flags = 1
+ data = length 195, hash F56DB8A5
+ sample 125:
+ time = 1702666
+ flags = 1
+ data = length 198, hash C8970FF7
+ sample 126:
+ time = 1724000
+ flags = 1
+ data = length 202, hash AF425C68
+ sample 127:
+ time = 1745333
+ flags = 1
+ data = length 196, hash 4215D839
+ sample 128:
+ time = 1766666
+ flags = 1
+ data = length 204, hash DB9BE8E3
+ sample 129:
+ time = 1788000
+ flags = 1
+ data = length 206, hash E5B20AB8
+ sample 130:
+ time = 1809333
+ flags = 1
+ data = length 209, hash D7F47B95
+ sample 131:
+ time = 1830666
+ flags = 1
+ data = length 193, hash FB54FB05
+ sample 132:
+ time = 1852000
+ flags = 1
+ data = length 199, hash D99C3106
+ sample 133:
+ time = 1873333
+ flags = 1
+ data = length 206, hash 253885B9
+ sample 134:
+ time = 1894666
+ flags = 1
+ data = length 191, hash FBDD8162
+ sample 135:
+ time = 1916000
+ flags = 1
+ data = length 183, hash 7290332F
+ sample 136:
+ time = 1937333
+ flags = 1
+ data = length 189, hash 1A9DC3DE
+ sample 137:
+ time = 1958666
+ flags = 1
+ data = length 201, hash 5D936764
+ sample 138:
+ time = 1980000
+ flags = 1
+ data = length 193, hash 6B03E75E
+ sample 139:
+ time = 2001333
+ flags = 1
+ data = length 199, hash 8A21BA83
+ sample 140:
+ time = 2022666
+ flags = 1
+ data = length 41, hash E6362210
+ sample 141:
+ time = 2034666
+ flags = 1
+ data = length 43, hash 36A57B44
+ sample 142:
+ time = 2037333
+ flags = 1
+ data = length 43, hash E51797D5
+ sample 143:
+ time = 2040000
+ flags = 1
+ data = length 43, hash 1F336C72
+ sample 144:
+ time = 2042666
+ flags = 1
+ data = length 42, hash 201AD367
+ sample 145:
+ time = 2045333
+ flags = 1
+ data = length 50, hash 606CCD6
+ sample 146:
+ time = 2048000
+ flags = 1
+ data = length 56, hash B15EBD7A
+ sample 147:
+ time = 2050666
+ flags = 1
+ data = length 212, hash 273B8D22
+ sample 148:
+ time = 2062666
+ flags = 1
+ data = length 194, hash 44F9CE1
+ sample 149:
+ time = 2084000
+ flags = 1
+ data = length 195, hash EDF9EBA1
+ sample 150:
+ time = 2105333
+ flags = 1
+ data = length 194, hash CE9F2D26
+ sample 151:
+ time = 2126666
+ flags = 1
+ data = length 192, hash 204F8A23
+ sample 152:
+ time = 2148000
+ flags = 1
+ data = length 206, hash DFA57E67
+ sample 153:
+ time = 2169333
+ flags = 1
+ data = length 196, hash 3CF084AB
+ sample 154:
+ time = 2190666
+ flags = 1
+ data = length 202, hash 2AF75C08
+ sample 155:
+ time = 2212000
+ flags = 1
+ data = length 203, hash 748EAF7
+ sample 156:
+ time = 2233333
+ flags = 1
+ data = length 205, hash ED82379D
+ sample 157:
+ time = 2254666
+ flags = 1
+ data = length 193, hash 61F26F22
+ sample 158:
+ time = 2276000
+ flags = 1
+ data = length 189, hash 85EF1D20
+ sample 159:
+ time = 2297333
+ flags = 1
+ data = length 187, hash 25E41FBF
+ sample 160:
+ time = 2318666
+ flags = 1
+ data = length 199, hash F365808
+ sample 161:
+ time = 2340000
+ flags = 1
+ data = length 197, hash 94205329
+ sample 162:
+ time = 2361333
+ flags = 1
+ data = length 201, hash FA2B2055
+ sample 163:
+ time = 2382666
+ flags = 1
+ data = length 194, hash AF95381F
+ sample 164:
+ time = 2404000
+ flags = 1
+ data = length 201, hash 923D3534
+ sample 165:
+ time = 2425333
+ flags = 1
+ data = length 198, hash 35F84C2E
+ sample 166:
+ time = 2446666
+ flags = 1
+ data = length 204, hash 6642CA40
+ sample 167:
+ time = 2468000
+ flags = 1
+ data = length 183, hash 3E2DC6BE
+ sample 168:
+ time = 2489333
+ flags = 1
+ data = length 197, hash B1E458CE
+ sample 169:
+ time = 2510666
+ flags = 1
+ data = length 193, hash E9218C84
+ sample 170:
+ time = 2532000
+ flags = 1
+ data = length 192, hash FEF08D4B
+ sample 171:
+ time = 2553333
+ flags = 1
+ data = length 201, hash FC411147
+ sample 172:
+ time = 2574666
+ flags = 1
+ data = length 218, hash 86893464
+ sample 173:
+ time = 2596000
+ flags = 1
+ data = length 226, hash 31C5320
+ sample 174:
+ time = 2617333
+ flags = 1
+ data = length 233, hash 9432BEE5
+ sample 175:
+ time = 2638666
+ flags = 1
+ data = length 213, hash B3FCC53E
+ sample 176:
+ time = 2660000
+ flags = 1
+ data = length 204, hash D70DD5A2
+ sample 177:
+ time = 2681333
+ flags = 1
+ data = length 212, hash A4EF1B69
+ sample 178:
+ time = 2702666
+ flags = 1
+ data = length 203, hash 8B0748B5
+ sample 179:
+ time = 2724000
+ flags = 1
+ data = length 149, hash E455335B
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ogg/continued_packet_at_start b/tree/testdata/src/test/assets/ogg/continued_packet_at_start
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/continued_packet_at_start
rename to tree/testdata/src/test/assets/ogg/continued_packet_at_start
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/continued_packet_over_four_pages b/tree/testdata/src/test/assets/ogg/continued_packet_over_four_pages
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/continued_packet_over_four_pages
rename to tree/testdata/src/test/assets/ogg/continued_packet_over_four_pages
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/continued_packet_over_two_pages b/tree/testdata/src/test/assets/ogg/continued_packet_over_two_pages
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/continued_packet_over_two_pages
rename to tree/testdata/src/test/assets/ogg/continued_packet_over_two_pages
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/eof_header b/tree/testdata/src/test/assets/ogg/eof_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/eof_header
rename to tree/testdata/src/test/assets/ogg/eof_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/flac_header b/tree/testdata/src/test/assets/ogg/flac_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/flac_header
rename to tree/testdata/src/test/assets/ogg/flac_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/four_packets_with_empty_page b/tree/testdata/src/test/assets/ogg/four_packets_with_empty_page
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/four_packets_with_empty_page
rename to tree/testdata/src/test/assets/ogg/four_packets_with_empty_page
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/invalid_header b/tree/testdata/src/test/assets/ogg/invalid_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/invalid_header
rename to tree/testdata/src/test/assets/ogg/invalid_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/invalid_ogg_header b/tree/testdata/src/test/assets/ogg/invalid_ogg_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/invalid_ogg_header
rename to tree/testdata/src/test/assets/ogg/invalid_ogg_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/opus_header b/tree/testdata/src/test/assets/ogg/opus_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/opus_header
rename to tree/testdata/src/test/assets/ogg/opus_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/packet_with_zero_size_terminator b/tree/testdata/src/test/assets/ogg/packet_with_zero_size_terminator
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/packet_with_zero_size_terminator
rename to tree/testdata/src/test/assets/ogg/packet_with_zero_size_terminator
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/page_header b/tree/testdata/src/test/assets/ogg/page_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/page_header
rename to tree/testdata/src/test/assets/ogg/page_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/random_1000_pages b/tree/testdata/src/test/assets/ogg/random_1000_pages
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/random_1000_pages
rename to tree/testdata/src/test/assets/ogg/random_1000_pages
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/three_headers b/tree/testdata/src/test/assets/ogg/three_headers
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/three_headers
rename to tree/testdata/src/test/assets/ogg/three_headers
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/vorbis_header b/tree/testdata/src/test/assets/ogg/vorbis_header
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/vorbis_header
rename to tree/testdata/src/test/assets/ogg/vorbis_header
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ogg/zero_sized_packets_at_end_of_stream b/tree/testdata/src/test/assets/ogg/zero_sized_packets_at_end_of_stream
similarity index 100%
rename from tree/library/extractor/src/test/assets/ogg/zero_sized_packets_at_end_of_stream
rename to tree/testdata/src/test/assets/ogg/zero_sized_packets_at_end_of_stream
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/rawcc/sample.rawcc b/tree/testdata/src/test/assets/rawcc/sample.rawcc
similarity index 100%
rename from tree/library/extractor/src/test/assets/rawcc/sample.rawcc
rename to tree/testdata/src/test/assets/rawcc/sample.rawcc
Binary files differ
diff --git a/tree/testdata/src/test/assets/rawcc/sample.rawcc.0.dump b/tree/testdata/src/test/assets/rawcc/sample.rawcc.0.dump
new file mode 100644
index 0000000..32c591b
--- /dev/null
+++ b/tree/testdata/src/test/assets/rawcc/sample.rawcc.0.dump
@@ -0,0 +1,612 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 978
+ sample count = 150
+ format 0:
+ sampleMimeType = application/cea-608
+ codecs = cea608
+ sample 0:
+ time = 37657512133
+ flags = 1
+ data = length 3, hash 7363
+ sample 1:
+ time = 37657528822
+ flags = 1
+ data = length 3, hash 7724
+ sample 2:
+ time = 37657545511
+ flags = 1
+ data = length 3, hash 766F
+ sample 3:
+ time = 37657562177
+ flags = 1
+ data = length 3, hash 7724
+ sample 4:
+ time = 37657578866
+ flags = 1
+ data = length 3, hash 767E
+ sample 5:
+ time = 37657595555
+ flags = 1
+ data = length 3, hash 7724
+ sample 6:
+ time = 37657612244
+ flags = 1
+ data = length 15, hash E4359178
+ sample 7:
+ time = 37657628911
+ flags = 1
+ data = length 3, hash 7724
+ sample 8:
+ time = 37657645600
+ flags = 1
+ data = length 12, hash 15EBEB66
+ sample 9:
+ time = 37657662288
+ flags = 1
+ data = length 3, hash 7724
+ sample 10:
+ time = 37657678977
+ flags = 1
+ data = length 3, hash 761D
+ sample 11:
+ time = 37657695644
+ flags = 1
+ data = length 3, hash 7724
+ sample 12:
+ time = 37657712333
+ flags = 1
+ data = length 30, hash E181418F
+ sample 13:
+ time = 37657729022
+ flags = 1
+ data = length 6, hash 36289CE2
+ sample 14:
+ time = 37657745711
+ flags = 1
+ data = length 12, hash 3C304F5B
+ sample 15:
+ time = 37657762377
+ flags = 1
+ data = length 3, hash 7724
+ sample 16:
+ time = 37657779066
+ flags = 1
+ data = length 12, hash 88DD8EF6
+ sample 17:
+ time = 37657795755
+ flags = 1
+ data = length 3, hash 7724
+ sample 18:
+ time = 37657812444
+ flags = 1
+ data = length 12, hash 8B411833
+ sample 19:
+ time = 37657829111
+ flags = 1
+ data = length 3, hash 7724
+ sample 20:
+ time = 37657845800
+ flags = 1
+ data = length 12, hash 742A2DF1
+ sample 21:
+ time = 37657862488
+ flags = 1
+ data = length 3, hash 7724
+ sample 22:
+ time = 37657879177
+ flags = 1
+ data = length 12, hash 9A2ECBEE
+ sample 23:
+ time = 37657895844
+ flags = 1
+ data = length 3, hash 7724
+ sample 24:
+ time = 37657912533
+ flags = 1
+ data = length 12, hash 562688EA
+ sample 25:
+ time = 37657929222
+ flags = 1
+ data = length 3, hash 7724
+ sample 26:
+ time = 37657945911
+ flags = 1
+ data = length 12, hash ADE4B953
+ sample 27:
+ time = 37657962577
+ flags = 1
+ data = length 3, hash 7724
+ sample 28:
+ time = 37657979266
+ flags = 1
+ data = length 12, hash F927E3E5
+ sample 29:
+ time = 37657995955
+ flags = 1
+ data = length 3, hash 7724
+ sample 30:
+ time = 37658012644
+ flags = 1
+ data = length 12, hash EA327945
+ sample 31:
+ time = 37658029311
+ flags = 1
+ data = length 3, hash 7724
+ sample 32:
+ time = 37658046000
+ flags = 1
+ data = length 12, hash 3E5DA13C
+ sample 33:
+ time = 37658062688
+ flags = 1
+ data = length 3, hash 7724
+ sample 34:
+ time = 37658079377
+ flags = 1
+ data = length 12, hash BF646AE3
+ sample 35:
+ time = 37658096044
+ flags = 1
+ data = length 3, hash 7724
+ sample 36:
+ time = 37658112733
+ flags = 1
+ data = length 12, hash 41E3BA78
+ sample 37:
+ time = 37658129422
+ flags = 1
+ data = length 3, hash 7724
+ sample 38:
+ time = 37658146111
+ flags = 1
+ data = length 12, hash A2945EF6
+ sample 39:
+ time = 37658162777
+ flags = 1
+ data = length 3, hash 7724
+ sample 40:
+ time = 37658179466
+ flags = 1
+ data = length 12, hash 26735812
+ sample 41:
+ time = 37658196155
+ flags = 1
+ data = length 3, hash 7724
+ sample 42:
+ time = 37658212844
+ flags = 1
+ data = length 12, hash DC14D3D8
+ sample 43:
+ time = 37658229511
+ flags = 1
+ data = length 3, hash 7724
+ sample 44:
+ time = 37658246200
+ flags = 1
+ data = length 12, hash 882191BE
+ sample 45:
+ time = 37658262888
+ flags = 1
+ data = length 3, hash 7724
+ sample 46:
+ time = 37658279577
+ flags = 1
+ data = length 12, hash 8B4886B1
+ sample 47:
+ time = 37658296244
+ flags = 1
+ data = length 3, hash 7724
+ sample 48:
+ time = 37658312933
+ flags = 1
+ data = length 12, hash 98D98F96
+ sample 49:
+ time = 37658329622
+ flags = 1
+ data = length 3, hash 7724
+ sample 50:
+ time = 37658346311
+ flags = 1
+ data = length 30, hash CF8E53E3
+ sample 51:
+ time = 37658362977
+ flags = 1
+ data = length 6, hash 36289CE2
+ sample 52:
+ time = 37658379666
+ flags = 1
+ data = length 12, hash F883C9EE
+ sample 53:
+ time = 37658396355
+ flags = 1
+ data = length 3, hash 7724
+ sample 54:
+ time = 37658413044
+ flags = 1
+ data = length 12, hash 6E6B2B9C
+ sample 55:
+ time = 37658429711
+ flags = 1
+ data = length 3, hash 7724
+ sample 56:
+ time = 37658446400
+ flags = 1
+ data = length 12, hash B4FE7F08
+ sample 57:
+ time = 37658463088
+ flags = 1
+ data = length 3, hash 7724
+ sample 58:
+ time = 37658479777
+ flags = 1
+ data = length 12, hash 5A1EA7C7
+ sample 59:
+ time = 37658496444
+ flags = 1
+ data = length 3, hash 7724
+ sample 60:
+ time = 37658513133
+ flags = 1
+ data = length 12, hash 46BD6CC9
+ sample 61:
+ time = 37658529822
+ flags = 1
+ data = length 3, hash 7724
+ sample 62:
+ time = 37658546511
+ flags = 1
+ data = length 12, hash 1B1E2554
+ sample 63:
+ time = 37658563177
+ flags = 1
+ data = length 3, hash 7724
+ sample 64:
+ time = 37658579866
+ flags = 1
+ data = length 12, hash 91FCC537
+ sample 65:
+ time = 37658596555
+ flags = 1
+ data = length 3, hash 7724
+ sample 66:
+ time = 37658613244
+ flags = 1
+ data = length 12, hash A9355E1B
+ sample 67:
+ time = 37658629911
+ flags = 1
+ data = length 3, hash 7724
+ sample 68:
+ time = 37658646600
+ flags = 1
+ data = length 12, hash 2511F69B
+ sample 69:
+ time = 37658663288
+ flags = 1
+ data = length 3, hash 7724
+ sample 70:
+ time = 37658679977
+ flags = 1
+ data = length 12, hash 90925736
+ sample 71:
+ time = 37658696644
+ flags = 1
+ data = length 3, hash 7724
+ sample 72:
+ time = 37658713333
+ flags = 1
+ data = length 21, hash 431EEE30
+ sample 73:
+ time = 37658730022
+ flags = 1
+ data = length 3, hash 7724
+ sample 74:
+ time = 37658746711
+ flags = 1
+ data = length 12, hash 7BDEF631
+ sample 75:
+ time = 37658763377
+ flags = 1
+ data = length 3, hash 7724
+ sample 76:
+ time = 37658780066
+ flags = 1
+ data = length 12, hash A2EEF59E
+ sample 77:
+ time = 37658796755
+ flags = 1
+ data = length 3, hash 7724
+ sample 78:
+ time = 37658813444
+ flags = 1
+ data = length 12, hash BFC6C022
+ sample 79:
+ time = 37658830111
+ flags = 1
+ data = length 3, hash 7724
+ sample 80:
+ time = 37658846800
+ flags = 1
+ data = length 12, hash CD4D8FCA
+ sample 81:
+ time = 37658863488
+ flags = 1
+ data = length 3, hash 7724
+ sample 82:
+ time = 37658880177
+ flags = 1
+ data = length 12, hash 2BDE8EFA
+ sample 83:
+ time = 37658896844
+ flags = 1
+ data = length 3, hash 7724
+ sample 84:
+ time = 37658913533
+ flags = 1
+ data = length 12, hash 8C858812
+ sample 85:
+ time = 37658930222
+ flags = 1
+ data = length 3, hash 7724
+ sample 86:
+ time = 37658946911
+ flags = 1
+ data = length 12, hash DE7D0E31
+ sample 87:
+ time = 37658963577
+ flags = 1
+ data = length 3, hash 7724
+ sample 88:
+ time = 37658980266
+ flags = 1
+ data = length 3, hash 7363
+ sample 89:
+ time = 37658996955
+ flags = 1
+ data = length 3, hash 7724
+ sample 90:
+ time = 37659013644
+ flags = 1
+ data = length 3, hash 7363
+ sample 91:
+ time = 37659030311
+ flags = 1
+ data = length 3, hash 7724
+ sample 92:
+ time = 37659047000
+ flags = 1
+ data = length 3, hash 7363
+ sample 93:
+ time = 37659063688
+ flags = 1
+ data = length 3, hash 7724
+ sample 94:
+ time = 37659080377
+ flags = 1
+ data = length 3, hash 7363
+ sample 95:
+ time = 37659097044
+ flags = 1
+ data = length 3, hash 7724
+ sample 96:
+ time = 37659113733
+ flags = 1
+ data = length 3, hash 7363
+ sample 97:
+ time = 37659130422
+ flags = 1
+ data = length 3, hash 7724
+ sample 98:
+ time = 37659147111
+ flags = 1
+ data = length 3, hash 7363
+ sample 99:
+ time = 37659163777
+ flags = 1
+ data = length 3, hash 7724
+ sample 100:
+ time = 37659180466
+ flags = 1
+ data = length 3, hash 7363
+ sample 101:
+ time = 37659197155
+ flags = 1
+ data = length 3, hash 7724
+ sample 102:
+ time = 37659213844
+ flags = 1
+ data = length 3, hash 7363
+ sample 103:
+ time = 37659230511
+ flags = 1
+ data = length 3, hash 7724
+ sample 104:
+ time = 37659247200
+ flags = 1
+ data = length 3, hash 7363
+ sample 105:
+ time = 37659263888
+ flags = 1
+ data = length 3, hash 7724
+ sample 106:
+ time = 37659280577
+ flags = 1
+ data = length 3, hash 7363
+ sample 107:
+ time = 37659297244
+ flags = 1
+ data = length 3, hash 7724
+ sample 108:
+ time = 37659313933
+ flags = 1
+ data = length 3, hash 7363
+ sample 109:
+ time = 37659330622
+ flags = 1
+ data = length 3, hash 7724
+ sample 110:
+ time = 37659347311
+ flags = 1
+ data = length 3, hash 7363
+ sample 111:
+ time = 37659363977
+ flags = 1
+ data = length 3, hash 7724
+ sample 112:
+ time = 37659380666
+ flags = 1
+ data = length 3, hash 7363
+ sample 113:
+ time = 37659397355
+ flags = 1
+ data = length 3, hash 7724
+ sample 114:
+ time = 37659414044
+ flags = 1
+ data = length 3, hash 7363
+ sample 115:
+ time = 37659430711
+ flags = 1
+ data = length 3, hash 7724
+ sample 116:
+ time = 37659447400
+ flags = 1
+ data = length 3, hash 7363
+ sample 117:
+ time = 37659464088
+ flags = 1
+ data = length 3, hash 7724
+ sample 118:
+ time = 37659480777
+ flags = 1
+ data = length 3, hash 7363
+ sample 119:
+ time = 37659497444
+ flags = 1
+ data = length 3, hash 7724
+ sample 120:
+ time = 37659514133
+ flags = 1
+ data = length 3, hash 7363
+ sample 121:
+ time = 37659530822
+ flags = 1
+ data = length 3, hash 7724
+ sample 122:
+ time = 37659547511
+ flags = 1
+ data = length 3, hash 7363
+ sample 123:
+ time = 37659564177
+ flags = 1
+ data = length 3, hash 7724
+ sample 124:
+ time = 37659580866
+ flags = 1
+ data = length 3, hash 7363
+ sample 125:
+ time = 37659597555
+ flags = 1
+ data = length 3, hash 7724
+ sample 126:
+ time = 37659614244
+ flags = 1
+ data = length 3, hash 766F
+ sample 127:
+ time = 37659630911
+ flags = 1
+ data = length 3, hash 7724
+ sample 128:
+ time = 37659647600
+ flags = 1
+ data = length 3, hash 767E
+ sample 129:
+ time = 37659664288
+ flags = 1
+ data = length 3, hash 7724
+ sample 130:
+ time = 37659680977
+ flags = 1
+ data = length 15, hash 191B585A
+ sample 131:
+ time = 37659697644
+ flags = 1
+ data = length 3, hash 7724
+ sample 132:
+ time = 37659714333
+ flags = 1
+ data = length 12, hash 15EC5FC5
+ sample 133:
+ time = 37659731022
+ flags = 1
+ data = length 3, hash 7724
+ sample 134:
+ time = 37659747711
+ flags = 1
+ data = length 3, hash 76A1
+ sample 135:
+ time = 37659764377
+ flags = 1
+ data = length 3, hash 7724
+ sample 136:
+ time = 37659781066
+ flags = 1
+ data = length 30, hash E8012479
+ sample 137:
+ time = 37659797755
+ flags = 1
+ data = length 6, hash 36289D5E
+ sample 138:
+ time = 37659814444
+ flags = 1
+ data = length 12, hash D32F29F3
+ sample 139:
+ time = 37659831111
+ flags = 1
+ data = length 3, hash 7724
+ sample 140:
+ time = 37659847800
+ flags = 1
+ data = length 21, hash 6258623
+ sample 141:
+ time = 37659864488
+ flags = 1
+ data = length 3, hash 7724
+ sample 142:
+ time = 37659881177
+ flags = 1
+ data = length 12, hash FE69ABA2
+ sample 143:
+ time = 37659897844
+ flags = 1
+ data = length 3, hash 7724
+ sample 144:
+ time = 37659914533
+ flags = 1
+ data = length 12, hash 958D0815
+ sample 145:
+ time = 37659931222
+ flags = 1
+ data = length 3, hash 7724
+ sample 146:
+ time = 37659947911
+ flags = 1
+ data = length 12, hash FF57BFD8
+ sample 147:
+ time = 37659964577
+ flags = 1
+ data = length 3, hash 7724
+ sample 148:
+ time = 37659981266
+ flags = 1
+ data = length 12, hash 922122E7
+ sample 149:
+ time = 37659997955
+ flags = 1
+ data = length 3, hash 7724
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/rawcc/sample.rawcc.unknown_length.dump b/tree/testdata/src/test/assets/rawcc/sample.rawcc.unknown_length.dump
new file mode 100644
index 0000000..32c591b
--- /dev/null
+++ b/tree/testdata/src/test/assets/rawcc/sample.rawcc.unknown_length.dump
@@ -0,0 +1,612 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 978
+ sample count = 150
+ format 0:
+ sampleMimeType = application/cea-608
+ codecs = cea608
+ sample 0:
+ time = 37657512133
+ flags = 1
+ data = length 3, hash 7363
+ sample 1:
+ time = 37657528822
+ flags = 1
+ data = length 3, hash 7724
+ sample 2:
+ time = 37657545511
+ flags = 1
+ data = length 3, hash 766F
+ sample 3:
+ time = 37657562177
+ flags = 1
+ data = length 3, hash 7724
+ sample 4:
+ time = 37657578866
+ flags = 1
+ data = length 3, hash 767E
+ sample 5:
+ time = 37657595555
+ flags = 1
+ data = length 3, hash 7724
+ sample 6:
+ time = 37657612244
+ flags = 1
+ data = length 15, hash E4359178
+ sample 7:
+ time = 37657628911
+ flags = 1
+ data = length 3, hash 7724
+ sample 8:
+ time = 37657645600
+ flags = 1
+ data = length 12, hash 15EBEB66
+ sample 9:
+ time = 37657662288
+ flags = 1
+ data = length 3, hash 7724
+ sample 10:
+ time = 37657678977
+ flags = 1
+ data = length 3, hash 761D
+ sample 11:
+ time = 37657695644
+ flags = 1
+ data = length 3, hash 7724
+ sample 12:
+ time = 37657712333
+ flags = 1
+ data = length 30, hash E181418F
+ sample 13:
+ time = 37657729022
+ flags = 1
+ data = length 6, hash 36289CE2
+ sample 14:
+ time = 37657745711
+ flags = 1
+ data = length 12, hash 3C304F5B
+ sample 15:
+ time = 37657762377
+ flags = 1
+ data = length 3, hash 7724
+ sample 16:
+ time = 37657779066
+ flags = 1
+ data = length 12, hash 88DD8EF6
+ sample 17:
+ time = 37657795755
+ flags = 1
+ data = length 3, hash 7724
+ sample 18:
+ time = 37657812444
+ flags = 1
+ data = length 12, hash 8B411833
+ sample 19:
+ time = 37657829111
+ flags = 1
+ data = length 3, hash 7724
+ sample 20:
+ time = 37657845800
+ flags = 1
+ data = length 12, hash 742A2DF1
+ sample 21:
+ time = 37657862488
+ flags = 1
+ data = length 3, hash 7724
+ sample 22:
+ time = 37657879177
+ flags = 1
+ data = length 12, hash 9A2ECBEE
+ sample 23:
+ time = 37657895844
+ flags = 1
+ data = length 3, hash 7724
+ sample 24:
+ time = 37657912533
+ flags = 1
+ data = length 12, hash 562688EA
+ sample 25:
+ time = 37657929222
+ flags = 1
+ data = length 3, hash 7724
+ sample 26:
+ time = 37657945911
+ flags = 1
+ data = length 12, hash ADE4B953
+ sample 27:
+ time = 37657962577
+ flags = 1
+ data = length 3, hash 7724
+ sample 28:
+ time = 37657979266
+ flags = 1
+ data = length 12, hash F927E3E5
+ sample 29:
+ time = 37657995955
+ flags = 1
+ data = length 3, hash 7724
+ sample 30:
+ time = 37658012644
+ flags = 1
+ data = length 12, hash EA327945
+ sample 31:
+ time = 37658029311
+ flags = 1
+ data = length 3, hash 7724
+ sample 32:
+ time = 37658046000
+ flags = 1
+ data = length 12, hash 3E5DA13C
+ sample 33:
+ time = 37658062688
+ flags = 1
+ data = length 3, hash 7724
+ sample 34:
+ time = 37658079377
+ flags = 1
+ data = length 12, hash BF646AE3
+ sample 35:
+ time = 37658096044
+ flags = 1
+ data = length 3, hash 7724
+ sample 36:
+ time = 37658112733
+ flags = 1
+ data = length 12, hash 41E3BA78
+ sample 37:
+ time = 37658129422
+ flags = 1
+ data = length 3, hash 7724
+ sample 38:
+ time = 37658146111
+ flags = 1
+ data = length 12, hash A2945EF6
+ sample 39:
+ time = 37658162777
+ flags = 1
+ data = length 3, hash 7724
+ sample 40:
+ time = 37658179466
+ flags = 1
+ data = length 12, hash 26735812
+ sample 41:
+ time = 37658196155
+ flags = 1
+ data = length 3, hash 7724
+ sample 42:
+ time = 37658212844
+ flags = 1
+ data = length 12, hash DC14D3D8
+ sample 43:
+ time = 37658229511
+ flags = 1
+ data = length 3, hash 7724
+ sample 44:
+ time = 37658246200
+ flags = 1
+ data = length 12, hash 882191BE
+ sample 45:
+ time = 37658262888
+ flags = 1
+ data = length 3, hash 7724
+ sample 46:
+ time = 37658279577
+ flags = 1
+ data = length 12, hash 8B4886B1
+ sample 47:
+ time = 37658296244
+ flags = 1
+ data = length 3, hash 7724
+ sample 48:
+ time = 37658312933
+ flags = 1
+ data = length 12, hash 98D98F96
+ sample 49:
+ time = 37658329622
+ flags = 1
+ data = length 3, hash 7724
+ sample 50:
+ time = 37658346311
+ flags = 1
+ data = length 30, hash CF8E53E3
+ sample 51:
+ time = 37658362977
+ flags = 1
+ data = length 6, hash 36289CE2
+ sample 52:
+ time = 37658379666
+ flags = 1
+ data = length 12, hash F883C9EE
+ sample 53:
+ time = 37658396355
+ flags = 1
+ data = length 3, hash 7724
+ sample 54:
+ time = 37658413044
+ flags = 1
+ data = length 12, hash 6E6B2B9C
+ sample 55:
+ time = 37658429711
+ flags = 1
+ data = length 3, hash 7724
+ sample 56:
+ time = 37658446400
+ flags = 1
+ data = length 12, hash B4FE7F08
+ sample 57:
+ time = 37658463088
+ flags = 1
+ data = length 3, hash 7724
+ sample 58:
+ time = 37658479777
+ flags = 1
+ data = length 12, hash 5A1EA7C7
+ sample 59:
+ time = 37658496444
+ flags = 1
+ data = length 3, hash 7724
+ sample 60:
+ time = 37658513133
+ flags = 1
+ data = length 12, hash 46BD6CC9
+ sample 61:
+ time = 37658529822
+ flags = 1
+ data = length 3, hash 7724
+ sample 62:
+ time = 37658546511
+ flags = 1
+ data = length 12, hash 1B1E2554
+ sample 63:
+ time = 37658563177
+ flags = 1
+ data = length 3, hash 7724
+ sample 64:
+ time = 37658579866
+ flags = 1
+ data = length 12, hash 91FCC537
+ sample 65:
+ time = 37658596555
+ flags = 1
+ data = length 3, hash 7724
+ sample 66:
+ time = 37658613244
+ flags = 1
+ data = length 12, hash A9355E1B
+ sample 67:
+ time = 37658629911
+ flags = 1
+ data = length 3, hash 7724
+ sample 68:
+ time = 37658646600
+ flags = 1
+ data = length 12, hash 2511F69B
+ sample 69:
+ time = 37658663288
+ flags = 1
+ data = length 3, hash 7724
+ sample 70:
+ time = 37658679977
+ flags = 1
+ data = length 12, hash 90925736
+ sample 71:
+ time = 37658696644
+ flags = 1
+ data = length 3, hash 7724
+ sample 72:
+ time = 37658713333
+ flags = 1
+ data = length 21, hash 431EEE30
+ sample 73:
+ time = 37658730022
+ flags = 1
+ data = length 3, hash 7724
+ sample 74:
+ time = 37658746711
+ flags = 1
+ data = length 12, hash 7BDEF631
+ sample 75:
+ time = 37658763377
+ flags = 1
+ data = length 3, hash 7724
+ sample 76:
+ time = 37658780066
+ flags = 1
+ data = length 12, hash A2EEF59E
+ sample 77:
+ time = 37658796755
+ flags = 1
+ data = length 3, hash 7724
+ sample 78:
+ time = 37658813444
+ flags = 1
+ data = length 12, hash BFC6C022
+ sample 79:
+ time = 37658830111
+ flags = 1
+ data = length 3, hash 7724
+ sample 80:
+ time = 37658846800
+ flags = 1
+ data = length 12, hash CD4D8FCA
+ sample 81:
+ time = 37658863488
+ flags = 1
+ data = length 3, hash 7724
+ sample 82:
+ time = 37658880177
+ flags = 1
+ data = length 12, hash 2BDE8EFA
+ sample 83:
+ time = 37658896844
+ flags = 1
+ data = length 3, hash 7724
+ sample 84:
+ time = 37658913533
+ flags = 1
+ data = length 12, hash 8C858812
+ sample 85:
+ time = 37658930222
+ flags = 1
+ data = length 3, hash 7724
+ sample 86:
+ time = 37658946911
+ flags = 1
+ data = length 12, hash DE7D0E31
+ sample 87:
+ time = 37658963577
+ flags = 1
+ data = length 3, hash 7724
+ sample 88:
+ time = 37658980266
+ flags = 1
+ data = length 3, hash 7363
+ sample 89:
+ time = 37658996955
+ flags = 1
+ data = length 3, hash 7724
+ sample 90:
+ time = 37659013644
+ flags = 1
+ data = length 3, hash 7363
+ sample 91:
+ time = 37659030311
+ flags = 1
+ data = length 3, hash 7724
+ sample 92:
+ time = 37659047000
+ flags = 1
+ data = length 3, hash 7363
+ sample 93:
+ time = 37659063688
+ flags = 1
+ data = length 3, hash 7724
+ sample 94:
+ time = 37659080377
+ flags = 1
+ data = length 3, hash 7363
+ sample 95:
+ time = 37659097044
+ flags = 1
+ data = length 3, hash 7724
+ sample 96:
+ time = 37659113733
+ flags = 1
+ data = length 3, hash 7363
+ sample 97:
+ time = 37659130422
+ flags = 1
+ data = length 3, hash 7724
+ sample 98:
+ time = 37659147111
+ flags = 1
+ data = length 3, hash 7363
+ sample 99:
+ time = 37659163777
+ flags = 1
+ data = length 3, hash 7724
+ sample 100:
+ time = 37659180466
+ flags = 1
+ data = length 3, hash 7363
+ sample 101:
+ time = 37659197155
+ flags = 1
+ data = length 3, hash 7724
+ sample 102:
+ time = 37659213844
+ flags = 1
+ data = length 3, hash 7363
+ sample 103:
+ time = 37659230511
+ flags = 1
+ data = length 3, hash 7724
+ sample 104:
+ time = 37659247200
+ flags = 1
+ data = length 3, hash 7363
+ sample 105:
+ time = 37659263888
+ flags = 1
+ data = length 3, hash 7724
+ sample 106:
+ time = 37659280577
+ flags = 1
+ data = length 3, hash 7363
+ sample 107:
+ time = 37659297244
+ flags = 1
+ data = length 3, hash 7724
+ sample 108:
+ time = 37659313933
+ flags = 1
+ data = length 3, hash 7363
+ sample 109:
+ time = 37659330622
+ flags = 1
+ data = length 3, hash 7724
+ sample 110:
+ time = 37659347311
+ flags = 1
+ data = length 3, hash 7363
+ sample 111:
+ time = 37659363977
+ flags = 1
+ data = length 3, hash 7724
+ sample 112:
+ time = 37659380666
+ flags = 1
+ data = length 3, hash 7363
+ sample 113:
+ time = 37659397355
+ flags = 1
+ data = length 3, hash 7724
+ sample 114:
+ time = 37659414044
+ flags = 1
+ data = length 3, hash 7363
+ sample 115:
+ time = 37659430711
+ flags = 1
+ data = length 3, hash 7724
+ sample 116:
+ time = 37659447400
+ flags = 1
+ data = length 3, hash 7363
+ sample 117:
+ time = 37659464088
+ flags = 1
+ data = length 3, hash 7724
+ sample 118:
+ time = 37659480777
+ flags = 1
+ data = length 3, hash 7363
+ sample 119:
+ time = 37659497444
+ flags = 1
+ data = length 3, hash 7724
+ sample 120:
+ time = 37659514133
+ flags = 1
+ data = length 3, hash 7363
+ sample 121:
+ time = 37659530822
+ flags = 1
+ data = length 3, hash 7724
+ sample 122:
+ time = 37659547511
+ flags = 1
+ data = length 3, hash 7363
+ sample 123:
+ time = 37659564177
+ flags = 1
+ data = length 3, hash 7724
+ sample 124:
+ time = 37659580866
+ flags = 1
+ data = length 3, hash 7363
+ sample 125:
+ time = 37659597555
+ flags = 1
+ data = length 3, hash 7724
+ sample 126:
+ time = 37659614244
+ flags = 1
+ data = length 3, hash 766F
+ sample 127:
+ time = 37659630911
+ flags = 1
+ data = length 3, hash 7724
+ sample 128:
+ time = 37659647600
+ flags = 1
+ data = length 3, hash 767E
+ sample 129:
+ time = 37659664288
+ flags = 1
+ data = length 3, hash 7724
+ sample 130:
+ time = 37659680977
+ flags = 1
+ data = length 15, hash 191B585A
+ sample 131:
+ time = 37659697644
+ flags = 1
+ data = length 3, hash 7724
+ sample 132:
+ time = 37659714333
+ flags = 1
+ data = length 12, hash 15EC5FC5
+ sample 133:
+ time = 37659731022
+ flags = 1
+ data = length 3, hash 7724
+ sample 134:
+ time = 37659747711
+ flags = 1
+ data = length 3, hash 76A1
+ sample 135:
+ time = 37659764377
+ flags = 1
+ data = length 3, hash 7724
+ sample 136:
+ time = 37659781066
+ flags = 1
+ data = length 30, hash E8012479
+ sample 137:
+ time = 37659797755
+ flags = 1
+ data = length 6, hash 36289D5E
+ sample 138:
+ time = 37659814444
+ flags = 1
+ data = length 12, hash D32F29F3
+ sample 139:
+ time = 37659831111
+ flags = 1
+ data = length 3, hash 7724
+ sample 140:
+ time = 37659847800
+ flags = 1
+ data = length 21, hash 6258623
+ sample 141:
+ time = 37659864488
+ flags = 1
+ data = length 3, hash 7724
+ sample 142:
+ time = 37659881177
+ flags = 1
+ data = length 12, hash FE69ABA2
+ sample 143:
+ time = 37659897844
+ flags = 1
+ data = length 3, hash 7724
+ sample 144:
+ time = 37659914533
+ flags = 1
+ data = length 12, hash 958D0815
+ sample 145:
+ time = 37659931222
+ flags = 1
+ data = length 3, hash 7724
+ sample 146:
+ time = 37659947911
+ flags = 1
+ data = length 12, hash FF57BFD8
+ sample 147:
+ time = 37659964577
+ flags = 1
+ data = length 3, hash 7724
+ sample 148:
+ time = 37659981266
+ flags = 1
+ data = length 12, hash 922122E7
+ sample 149:
+ time = 37659997955
+ flags = 1
+ data = length 3, hash 7724
+tracksEnded = true
diff --git a/tree/library/smoothstreaming/src/test/assets/sample_ismc_1 b/tree/testdata/src/test/assets/smooth-streaming/sample_ismc_1
similarity index 100%
rename from tree/library/smoothstreaming/src/test/assets/sample_ismc_1
rename to tree/testdata/src/test/assets/smooth-streaming/sample_ismc_1
diff --git a/tree/library/smoothstreaming/src/test/assets/sample_ismc_2 b/tree/testdata/src/test/assets/smooth-streaming/sample_ismc_2
similarity index 100%
rename from tree/library/smoothstreaming/src/test/assets/sample_ismc_2
rename to tree/testdata/src/test/assets/smooth-streaming/sample_ismc_2
diff --git a/tree/library/core/src/test/assets/ssa/empty b/tree/testdata/src/test/assets/ssa/empty
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/empty
rename to tree/testdata/src/test/assets/ssa/empty
diff --git a/tree/library/core/src/test/assets/ssa/invalid_positioning b/tree/testdata/src/test/assets/ssa/invalid_positioning
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/invalid_positioning
rename to tree/testdata/src/test/assets/ssa/invalid_positioning
diff --git a/tree/library/core/src/test/assets/ssa/invalid_timecodes b/tree/testdata/src/test/assets/ssa/invalid_timecodes
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/invalid_timecodes
rename to tree/testdata/src/test/assets/ssa/invalid_timecodes
diff --git a/tree/library/core/src/test/assets/ssa/overlapping_timecodes b/tree/testdata/src/test/assets/ssa/overlapping_timecodes
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/overlapping_timecodes
rename to tree/testdata/src/test/assets/ssa/overlapping_timecodes
diff --git a/tree/library/core/src/test/assets/ssa/positioning b/tree/testdata/src/test/assets/ssa/positioning
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/positioning
rename to tree/testdata/src/test/assets/ssa/positioning
diff --git a/tree/library/core/src/test/assets/ssa/positioning_without_playres b/tree/testdata/src/test/assets/ssa/positioning_without_playres
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/positioning_without_playres
rename to tree/testdata/src/test/assets/ssa/positioning_without_playres
diff --git a/tree/library/core/src/test/assets/ssa/typical b/tree/testdata/src/test/assets/ssa/typical
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/typical
rename to tree/testdata/src/test/assets/ssa/typical
diff --git a/tree/library/core/src/test/assets/ssa/typical_dialogue b/tree/testdata/src/test/assets/ssa/typical_dialogue
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/typical_dialogue
rename to tree/testdata/src/test/assets/ssa/typical_dialogue
diff --git a/tree/library/core/src/test/assets/ssa/typical_format b/tree/testdata/src/test/assets/ssa/typical_format
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/typical_format
rename to tree/testdata/src/test/assets/ssa/typical_format
diff --git a/tree/library/core/src/test/assets/ssa/typical_header b/tree/testdata/src/test/assets/ssa/typical_header
similarity index 100%
rename from tree/library/core/src/test/assets/ssa/typical_header
rename to tree/testdata/src/test/assets/ssa/typical_header
diff --git a/tree/library/core/src/test/assets/subrip/empty b/tree/testdata/src/test/assets/subrip/empty
similarity index 100%
rename from tree/library/core/src/test/assets/subrip/empty
rename to tree/testdata/src/test/assets/subrip/empty
diff --git a/tree/testdata/src/test/assets/subrip/typical b/tree/testdata/src/test/assets/subrip/typical
new file mode 100644
index 0000000..cc9a3da
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical
@@ -0,0 +1,12 @@
+1
+00:00:00,000 --> 00:00:01,234
+This is the first subtitle.
+
+2
+00:00:02,345 --> 00:00:03,456
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/testdata/src/test/assets/subrip/typical_extra_blank_line b/tree/testdata/src/test/assets/subrip/typical_extra_blank_line
new file mode 100644
index 0000000..392cb7e
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_extra_blank_line
@@ -0,0 +1,13 @@
+1
+00:00:00,000 --> 00:00:01,234
+This is the first subtitle.
+
+
+2
+00:00:02,345 --> 00:00:03,456
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/testdata/src/test/assets/subrip/typical_missing_sequence b/tree/testdata/src/test/assets/subrip/typical_missing_sequence
new file mode 100644
index 0000000..e75711d
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_missing_sequence
@@ -0,0 +1,11 @@
+1
+00:00:00,000 --> 00:00:01,234
+This is the first subtitle.
+
+00:00:02,345 --> 00:00:03,456
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/testdata/src/test/assets/subrip/typical_missing_timecode b/tree/testdata/src/test/assets/subrip/typical_missing_timecode
new file mode 100644
index 0000000..bce61a7
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_missing_timecode
@@ -0,0 +1,19 @@
+1
+00:00:00,000 --> 00:00:01,234
+This is the first subtitle.
+
+2
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
+
+4
+ --> 02:00:10,901
+This is the fourth subtitle.
+
+5
+02:00:12,901 -->
+This is the fifth subtitle.
diff --git a/tree/testdata/src/test/assets/subrip/typical_negative_timestamps b/tree/testdata/src/test/assets/subrip/typical_negative_timestamps
new file mode 100644
index 0000000..1df7bf6
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_negative_timestamps
@@ -0,0 +1,12 @@
+1
+-0:00:04,567 --> -0:00:03,456
+This is the first subtitle.
+
+2
+-00:00:02,345 --> 00:00:01,234
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/testdata/src/test/assets/subrip/typical_no_hours_and_millis b/tree/testdata/src/test/assets/subrip/typical_no_hours_and_millis
new file mode 100644
index 0000000..5340fc7
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_no_hours_and_millis
@@ -0,0 +1,12 @@
+1
+00:00,000 --> 00:01,234
+This is the first subtitle.
+
+2
+00:00:02 --> 00:00:03
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_unexpected_end b/tree/testdata/src/test/assets/subrip/typical_unexpected_end
similarity index 100%
rename from tree/library/core/src/test/assets/subrip/typical_unexpected_end
rename to tree/testdata/src/test/assets/subrip/typical_unexpected_end
diff --git a/tree/testdata/src/test/assets/subrip/typical_with_byte_order_mark b/tree/testdata/src/test/assets/subrip/typical_with_byte_order_mark
new file mode 100644
index 0000000..050e1c0
--- /dev/null
+++ b/tree/testdata/src/test/assets/subrip/typical_with_byte_order_mark
@@ -0,0 +1,12 @@
+1
+00:00:00,000 --> 00:00:01,234
+This is the first subtitle.
+
+2
+00:00:02,345 --> 00:00:03,456
+This is the second subtitle.
+Second subtitle with second line.
+
+3
+02:00:04,567 --> 02:00:08,901
+This is the third subtitle.
diff --git a/tree/library/core/src/test/assets/subrip/typical_with_tags b/tree/testdata/src/test/assets/subrip/typical_with_tags
similarity index 100%
rename from tree/library/core/src/test/assets/subrip/typical_with_tags
rename to tree/testdata/src/test/assets/subrip/typical_with_tags
diff --git a/tree/library/extractor/src/test/assets/ts/bbb_2500ms.ts b/tree/testdata/src/test/assets/ts/bbb_2500ms.ts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/bbb_2500ms.ts
rename to tree/testdata/src/test/assets/ts/bbb_2500ms.ts
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ts/elephants_dream.mpg b/tree/testdata/src/test/assets/ts/elephants_dream.mpg
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/elephants_dream.mpg
rename to tree/testdata/src/test/assets/ts/elephants_dream.mpg
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ac3 b/tree/testdata/src/test/assets/ts/sample.ac3
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.ac3
rename to tree/testdata/src/test/assets/ts/sample.ac3
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample.ac3.0.dump b/tree/testdata/src/test/assets/ts/sample.ac3.0.dump
new file mode 100644
index 0000000..3f582ca
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.ac3.0.dump
@@ -0,0 +1,46 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13281
+ sample count = 8
+ format 0:
+ id = 0
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample.ac3.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample.ac3.unknown_length.dump
new file mode 100644
index 0000000..3f582ca
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.ac3.unknown_length.dump
@@ -0,0 +1,46 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 13281
+ sample count = 8
+ format 0:
+ id = 0
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ac4 b/tree/testdata/src/test/assets/ts/sample.ac4
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.ac4
rename to tree/testdata/src/test/assets/ts/sample.ac4
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample.ac4.0.dump b/tree/testdata/src/test/assets/ts/sample.ac4.0.dump
new file mode 100644
index 0000000..3b82b0b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.ac4.0.dump
@@ -0,0 +1,90 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7594
+ sample count = 19
+ format 0:
+ id = 0
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 366, hash B4277F9E
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 366, hash E8E0A142
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 366, hash 2E5073D0
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 366, hash 850E71D8
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 366, hash 69CD444E
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 366, hash BD24F36D
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 366, hash E24F2490
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 366, hash EE6F1F06
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 366, hash 2DAB000F
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 366, hash 8102B7EC
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 366, hash 55BF59AC
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 494, hash CBC2E09F
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 519, hash 9DAF56E9
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample.ac4.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample.ac4.unknown_length.dump
new file mode 100644
index 0000000..3b82b0b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.ac4.unknown_length.dump
@@ -0,0 +1,90 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 7594
+ sample count = 19
+ format 0:
+ id = 0
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 366, hash B4277F9E
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 366, hash E8E0A142
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 366, hash 2E5073D0
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 366, hash 850E71D8
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 366, hash 69CD444E
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 366, hash BD24F36D
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 366, hash E24F2490
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 366, hash EE6F1F06
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 366, hash 2DAB000F
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 366, hash 8102B7EC
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 366, hash 55BF59AC
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 494, hash CBC2E09F
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 519, hash 9DAF56E9
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.adts b/tree/testdata/src/test/assets/ts/sample.adts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.adts
rename to tree/testdata/src/test/assets/ts/sample.adts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample.adts.0.dump b/tree/testdata/src/test/assets/ts/sample.adts.0.dump
new file mode 100644
index 0000000..ddfea3e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.adts.0.dump
@@ -0,0 +1,599 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample.adts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample.adts.unknown_length.dump
new file mode 100644
index 0000000..ddfea3e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.adts.unknown_length.dump
@@ -0,0 +1,599 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.eac3 b/tree/testdata/src/test/assets/ts/sample.eac3
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.eac3
rename to tree/testdata/src/test/assets/ts/sample.eac3
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample.eac3.0.dump b/tree/testdata/src/test/assets/ts/sample.eac3.0.dump
new file mode 100644
index 0000000..f3d9d39
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.eac3.0.dump
@@ -0,0 +1,230 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 0
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 5333
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 10666
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 15999
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 21332
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 26665
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 31998
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 37331
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 42664
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 47997
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 53330
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 58663
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 63996
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 69329
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 74662
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 79995
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 85328
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 90661
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 95994
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 101327
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 106660
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 111993
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 117326
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 122659
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 127992
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 133325
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 138658
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 143991
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 149324
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 154657
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 159990
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 165323
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 170656
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 175989
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 181322
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 186655
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 191988
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 197321
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 202654
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 207987
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 213320
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 218653
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 223986
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 229319
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 234652
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 239985
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 245318
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 250651
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 255984
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 261317
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 266650
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 271983
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 277316
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 282649
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample.eac3.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample.eac3.unknown_length.dump
new file mode 100644
index 0000000..f3d9d39
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample.eac3.unknown_length.dump
@@ -0,0 +1,230 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 0
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 5333
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 10666
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 15999
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 21332
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 26665
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 31998
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 37331
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 42664
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 47997
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 53330
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 58663
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 63996
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 69329
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 74662
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 79995
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 85328
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 90661
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 95994
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 101327
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 106660
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 111993
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 117326
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 122659
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 127992
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 133325
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 138658
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 143991
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 149324
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 154657
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 159990
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 165323
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 170656
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 175989
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 181322
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 186655
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 191988
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 197321
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 202654
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 207987
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 213320
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 218653
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 223986
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 229319
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 234652
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 239985
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 245318
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 250651
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 255984
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 261317
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 266650
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 271983
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 277316
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 282649
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps b/tree/testdata/src/test/assets/ts/sample_ac3.ps
new file mode 100644
index 0000000..a255996
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps.0.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ps.0.dump
new file mode 100644
index 0000000..27d0c45
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps.0.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 189:
+ total output bytes = 1252
+ sample count = 3
+ format 0:
+ id = 189
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 416, hash 6B14E268
+ sample 1:
+ time = 34829
+ flags = 1
+ data = length 418, hash BC27DF0B
+ sample 2:
+ time = 69658
+ flags = 1
+ data = length 418, hash BC27DF0B
+tracksEnded = false
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps.1.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ps.1.dump
new file mode 100644
index 0000000..27d0c45
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps.1.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 189:
+ total output bytes = 1252
+ sample count = 3
+ format 0:
+ id = 189
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 416, hash 6B14E268
+ sample 1:
+ time = 34829
+ flags = 1
+ data = length 418, hash BC27DF0B
+ sample 2:
+ time = 69658
+ flags = 1
+ data = length 418, hash BC27DF0B
+tracksEnded = false
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps.2.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ps.2.dump
new file mode 100644
index 0000000..27d0c45
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps.2.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 189:
+ total output bytes = 1252
+ sample count = 3
+ format 0:
+ id = 189
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 416, hash 6B14E268
+ sample 1:
+ time = 34829
+ flags = 1
+ data = length 418, hash BC27DF0B
+ sample 2:
+ time = 69658
+ flags = 1
+ data = length 418, hash BC27DF0B
+tracksEnded = false
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps.3.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ps.3.dump
new file mode 100644
index 0000000..27d0c45
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps.3.dump
@@ -0,0 +1,29 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 189:
+ total output bytes = 1252
+ sample count = 3
+ format 0:
+ id = 189
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 416, hash 6B14E268
+ sample 1:
+ time = 34829
+ flags = 1
+ data = length 418, hash BC27DF0B
+ sample 2:
+ time = 69658
+ flags = 1
+ data = length 418, hash BC27DF0B
+tracksEnded = false
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ps.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ps.unknown_length.dump
new file mode 100644
index 0000000..9608821
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ps.unknown_length.dump
@@ -0,0 +1,26 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 189:
+ total output bytes = 1252
+ sample count = 3
+ format 0:
+ id = 189
+ sampleMimeType = audio/ac3
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 416, hash 6B14E268
+ sample 1:
+ time = 34829
+ flags = 1
+ data = length 418, hash BC27DF0B
+ sample 2:
+ time = 69658
+ flags = 1
+ data = length 418, hash BC27DF0B
+tracksEnded = false
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts b/tree/testdata/src/test/assets/ts/sample_ac3.ts
new file mode 100644
index 0000000..48dc17c
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ts.0.dump
new file mode 100644
index 0000000..561963e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts.0.dump
@@ -0,0 +1,49 @@
+seekMap:
+ isSeekable = true
+ duration = 252977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(126488) = [[timeUs=126488, position=9099]]
+ getPosition(252977) = [[timeUs=252977, position=18386]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 13281
+ sample count = 8
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 96000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 128000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 192000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 256000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ts.1.dump
new file mode 100644
index 0000000..d778af8
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts.1.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 252977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(126488) = [[timeUs=126488, position=9099]]
+ getPosition(252977) = [[timeUs=252977, position=18386]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 10209
+ sample count = 6
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 96000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 1:
+ time = 128000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 2:
+ time = 160000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 3:
+ time = 192000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 4:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 5:
+ time = 256000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ts.2.dump
new file mode 100644
index 0000000..f48ba43
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts.2.dump
@@ -0,0 +1,33 @@
+seekMap:
+ isSeekable = true
+ duration = 252977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(126488) = [[timeUs=126488, position=9099]]
+ getPosition(252977) = [[timeUs=252977, position=18386]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 7137
+ sample count = 4
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 160000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 1:
+ time = 192000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 2:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 3:
+ time = 256000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ts.3.dump
new file mode 100644
index 0000000..997d7a6
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts.3.dump
@@ -0,0 +1,17 @@
+seekMap:
+ isSeekable = true
+ duration = 252977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(126488) = [[timeUs=126488, position=9099]]
+ getPosition(252977) = [[timeUs=252977, position=18386]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac3.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_ac3.ts.unknown_length.dump
new file mode 100644
index 0000000..a98cb79
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac3.ts.unknown_length.dump
@@ -0,0 +1,46 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 13281
+ sample count = 8
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 32000
+ flags = 1
+ data = length 1536, hash 7108D5C2
+ sample 1:
+ time = 64000
+ flags = 1
+ data = length 1536, hash 80BF3B34
+ sample 2:
+ time = 96000
+ flags = 1
+ data = length 1536, hash 5D09685
+ sample 3:
+ time = 128000
+ flags = 1
+ data = length 1536, hash A9A24E44
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 1536, hash 6F856273
+ sample 5:
+ time = 192000
+ flags = 1
+ data = length 1536, hash B1737D3C
+ sample 6:
+ time = 224000
+ flags = 1
+ data = length 1536, hash 98FDEB9D
+ sample 7:
+ time = 256000
+ flags = 1
+ data = length 1536, hash 99B9B943
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts b/tree/testdata/src/test/assets/ts/sample_ac4.ts
new file mode 100644
index 0000000..7be76c5
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_ac4.ts.0.dump
new file mode 100644
index 0000000..80978a8
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts.0.dump
@@ -0,0 +1,93 @@
+seekMap:
+ isSeekable = true
+ duration = 796888
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(398444) = [[timeUs=398444, position=13385]]
+ getPosition(796888) = [[timeUs=796888, position=26959]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 7594
+ sample count = 19
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 366, hash B4277F9E
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 366, hash E8E0A142
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 366, hash 2E5073D0
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 366, hash 850E71D8
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 366, hash 69CD444E
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 366, hash BD24F36D
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 366, hash E24F2490
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 366, hash EE6F1F06
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 366, hash 2DAB000F
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 366, hash 8102B7EC
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 366, hash 55BF59AC
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 494, hash CBC2E09F
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 519, hash 9DAF56E9
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_ac4.ts.1.dump
new file mode 100644
index 0000000..f9bb289
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts.1.dump
@@ -0,0 +1,65 @@
+seekMap:
+ isSeekable = true
+ duration = 796888
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(398444) = [[timeUs=398444, position=13385]]
+ getPosition(796888) = [[timeUs=796888, position=26959]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 5032
+ sample count = 12
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 280000
+ flags = 1
+ data = length 366, hash EE6F1F06
+ sample 1:
+ time = 320000
+ flags = 1
+ data = length 366, hash 2DAB000F
+ sample 2:
+ time = 360000
+ flags = 1
+ data = length 366, hash 8102B7EC
+ sample 3:
+ time = 400000
+ flags = 1
+ data = length 366, hash 55BF59AC
+ sample 4:
+ time = 440000
+ flags = 1
+ data = length 494, hash CBC2E09F
+ sample 5:
+ time = 480000
+ flags = 1
+ data = length 519, hash 9DAF56E9
+ sample 6:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 7:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 8:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 9:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 10:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 11:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_ac4.ts.2.dump
new file mode 100644
index 0000000..3c90f50
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts.2.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 796888
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(398444) = [[timeUs=398444, position=13385]]
+ getPosition(796888) = [[timeUs=796888, position=26959]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 2555
+ sample count = 6
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 1:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 2:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 3:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 4:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 5:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_ac4.ts.3.dump
new file mode 100644
index 0000000..8719930
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts.3.dump
@@ -0,0 +1,17 @@
+seekMap:
+ isSeekable = true
+ duration = 796888
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(398444) = [[timeUs=398444, position=13385]]
+ getPosition(796888) = [[timeUs=796888, position=26959]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ac4.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_ac4.ts.unknown_length.dump
new file mode 100644
index 0000000..3982b40
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ac4.ts.unknown_length.dump
@@ -0,0 +1,90 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 7594
+ sample count = 19
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/ac4
+ channelCount = 2
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 366, hash B4277F9E
+ sample 1:
+ time = 40000
+ flags = 1
+ data = length 366, hash E8E0A142
+ sample 2:
+ time = 80000
+ flags = 1
+ data = length 366, hash 2E5073D0
+ sample 3:
+ time = 120000
+ flags = 1
+ data = length 366, hash 850E71D8
+ sample 4:
+ time = 160000
+ flags = 1
+ data = length 366, hash 69CD444E
+ sample 5:
+ time = 200000
+ flags = 1
+ data = length 366, hash BD24F36D
+ sample 6:
+ time = 240000
+ flags = 1
+ data = length 366, hash E24F2490
+ sample 7:
+ time = 280000
+ flags = 1
+ data = length 366, hash EE6F1F06
+ sample 8:
+ time = 320000
+ flags = 1
+ data = length 366, hash 2DAB000F
+ sample 9:
+ time = 360000
+ flags = 1
+ data = length 366, hash 8102B7EC
+ sample 10:
+ time = 400000
+ flags = 1
+ data = length 366, hash 55BF59AC
+ sample 11:
+ time = 440000
+ flags = 1
+ data = length 494, hash CBC2E09F
+ sample 12:
+ time = 480000
+ flags = 1
+ data = length 519, hash 9DAF56E9
+ sample 13:
+ time = 520000
+ flags = 1
+ data = length 598, hash 8169EE2
+ sample 14:
+ time = 560000
+ flags = 1
+ data = length 435, hash 28C21246
+ sample 15:
+ time = 600000
+ flags = 1
+ data = length 365, hash FF14716D
+ sample 16:
+ time = 640000
+ flags = 1
+ data = length 392, hash 4CC96B29
+ sample 17:
+ time = 680000
+ flags = 1
+ data = length 373, hash D7AC6D4E
+ sample 18:
+ time = 720000
+ flags = 1
+ data = length 392, hash 99F2511F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ait.ts b/tree/testdata/src/test/assets/ts/sample_ait.ts
new file mode 100644
index 0000000..eb3678e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ait.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_ait.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_ait.ts.0.dump
new file mode 100644
index 0000000..355b403
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ait.ts.0.dump
@@ -0,0 +1,109 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 330:
+ total output bytes = 9928
+ sample count = 19
+ format 0:
+ id = 1031/330
+ sampleMimeType = audio/eac3
+ channelCount = 2
+ sampleRate = 48000
+ language = fr
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 512, hash E47547D4
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 512, hash F6A537AC
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 512, hash 97391682
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 512, hash CFD3B665
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 512, hash 2E79A3AF
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 512, hash 2C24E2A3
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 512, hash 5BCB9661
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 512, hash 943ACBF2
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 512, hash B248E943
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 512, hash EC2DD86F
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 512, hash A659332F
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 512, hash CB641607
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 512, hash 157489A0
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 512, hash A37CB66E
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 512, hash 932F07D4
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 512, hash 91F50161
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 512, hash 7F9D6CCB
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 512, hash 3955F015
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 512, hash A8E5C938
+track 370:
+ total output bytes = 1413
+ sample count = 3
+ format 0:
+ sampleMimeType = application/vnd.dvb.ait
+ subsampleOffsetUs = -43622564033
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 471, hash B189052F
+ sample 1:
+ time = 192000
+ flags = 1
+ data = length 471, hash B189052F
+ sample 2:
+ time = 384000
+ flags = 1
+ data = length 471, hash B189052F
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_ait.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_ait.ts.unknown_length.dump
new file mode 100644
index 0000000..355b403
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_ait.ts.unknown_length.dump
@@ -0,0 +1,109 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 330:
+ total output bytes = 9928
+ sample count = 19
+ format 0:
+ id = 1031/330
+ sampleMimeType = audio/eac3
+ channelCount = 2
+ sampleRate = 48000
+ language = fr
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 512, hash E47547D4
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 512, hash F6A537AC
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 512, hash 97391682
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 512, hash CFD3B665
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 512, hash 2E79A3AF
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 512, hash 2C24E2A3
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 512, hash 5BCB9661
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 512, hash 943ACBF2
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 512, hash B248E943
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 512, hash EC2DD86F
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 512, hash A659332F
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 512, hash CB641607
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 512, hash 157489A0
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 512, hash A37CB66E
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 512, hash 932F07D4
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 512, hash 91F50161
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 512, hash 7F9D6CCB
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 512, hash 3955F015
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 512, hash A8E5C938
+track 370:
+ total output bytes = 1413
+ sample count = 3
+ format 0:
+ sampleMimeType = application/vnd.dvb.ait
+ subsampleOffsetUs = -43622564033
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 471, hash B189052F
+ sample 1:
+ time = 192000
+ flags = 1
+ data = length 471, hash B189052F
+ sample 2:
+ time = 384000
+ flags = 1
+ data = length 471, hash B189052F
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs.adts b/tree/testdata/src/test/assets/ts/sample_cbs.adts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample_cbs.adts
rename to tree/testdata/src/test/assets/ts/sample_cbs.adts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs.adts.0.dump b/tree/testdata/src/test/assets/ts/sample_cbs.adts.0.dump
new file mode 100644
index 0000000..e3aaa10
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs.adts.0.dump
@@ -0,0 +1,602 @@
+seekMap:
+ isSeekable = true
+ duration = 3356772
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1678386) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3356772) = [[timeUs=3333553, position=31585]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs.adts.1.dump b/tree/testdata/src/test/assets/ts/sample_cbs.adts.1.dump
new file mode 100644
index 0000000..ab3d2fe
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs.adts.1.dump
@@ -0,0 +1,402 @@
+seekMap:
+ isSeekable = true
+ duration = 3356772
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1678386) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3356772) = [[timeUs=3333553, position=31585]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 20533
+ sample count = 94
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 1118924
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 1:
+ time = 1142143
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 2:
+ time = 1165362
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 3:
+ time = 1188581
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 4:
+ time = 1211800
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 5:
+ time = 1235019
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 6:
+ time = 1258238
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 7:
+ time = 1281457
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 8:
+ time = 1304676
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 9:
+ time = 1327895
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 10:
+ time = 1351114
+ flags = 1
+ data = length 212, hash 63214325
+ sample 11:
+ time = 1374333
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 12:
+ time = 1397552
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 13:
+ time = 1420771
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 14:
+ time = 1443990
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 15:
+ time = 1467209
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 16:
+ time = 1490428
+ flags = 1
+ data = length 195, hash A816647A
+ sample 17:
+ time = 1513647
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 18:
+ time = 1536866
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 19:
+ time = 1560085
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 20:
+ time = 1583304
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 21:
+ time = 1606523
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 22:
+ time = 1629742
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 23:
+ time = 1652961
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 24:
+ time = 1676180
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 25:
+ time = 1699399
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 26:
+ time = 1722618
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 27:
+ time = 1745837
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 28:
+ time = 1769056
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 29:
+ time = 1792275
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 30:
+ time = 1815494
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 31:
+ time = 1838713
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 32:
+ time = 1861932
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 33:
+ time = 1885151
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 34:
+ time = 1908370
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 35:
+ time = 1931589
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 36:
+ time = 1954808
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 37:
+ time = 1978027
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 38:
+ time = 2001246
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 39:
+ time = 2024465
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 40:
+ time = 2047684
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 41:
+ time = 2070903
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 42:
+ time = 2094122
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 43:
+ time = 2117341
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 44:
+ time = 2140560
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 45:
+ time = 2163779
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 46:
+ time = 2186998
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 47:
+ time = 2210217
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 48:
+ time = 2233436
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 49:
+ time = 2256655
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 50:
+ time = 2279874
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 51:
+ time = 2303093
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 52:
+ time = 2326312
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 53:
+ time = 2349531
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 54:
+ time = 2372750
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 55:
+ time = 2395969
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 56:
+ time = 2419188
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 57:
+ time = 2442407
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 58:
+ time = 2465626
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 59:
+ time = 2488845
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 60:
+ time = 2512064
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 61:
+ time = 2535283
+ flags = 1
+ data = length 211, hash BA050052
+ sample 62:
+ time = 2558502
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 63:
+ time = 2581721
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 64:
+ time = 2604940
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 65:
+ time = 2628159
+ flags = 1
+ data = length 220, hash 32087455
+ sample 66:
+ time = 2651378
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 67:
+ time = 2674597
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 68:
+ time = 2697816
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 69:
+ time = 2721035
+ flags = 1
+ data = length 219, hash 45382270
+ sample 70:
+ time = 2744254
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 71:
+ time = 2767473
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 72:
+ time = 2790692
+ flags = 1
+ data = length 211, hash F0435574
+ sample 73:
+ time = 2813911
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 74:
+ time = 2837130
+ flags = 1
+ data = length 200, hash 82553248
+ sample 75:
+ time = 2860349
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 76:
+ time = 2883568
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 77:
+ time = 2906787
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 78:
+ time = 2930006
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 79:
+ time = 2953225
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 80:
+ time = 2976444
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 81:
+ time = 2999663
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 82:
+ time = 3022882
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 83:
+ time = 3046101
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 84:
+ time = 3069320
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 85:
+ time = 3092539
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 86:
+ time = 3115758
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 87:
+ time = 3138977
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 88:
+ time = 3162196
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 89:
+ time = 3185415
+ flags = 1
+ data = length 161, hash E991035D
+ sample 90:
+ time = 3208634
+ flags = 1
+ data = length 197, hash C3013689
+ sample 91:
+ time = 3231853
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 92:
+ time = 3255072
+ flags = 1
+ data = length 232, hash A330F188
+ sample 93:
+ time = 3278291
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs.adts.2.dump b/tree/testdata/src/test/assets/ts/sample_cbs.adts.2.dump
new file mode 100644
index 0000000..2330cdc
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs.adts.2.dump
@@ -0,0 +1,222 @@
+seekMap:
+ isSeekable = true
+ duration = 3356772
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1678386) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3356772) = [[timeUs=3333553, position=31585]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 10161
+ sample count = 49
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 2237848
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 1:
+ time = 2261067
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 2:
+ time = 2284286
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 3:
+ time = 2307505
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 4:
+ time = 2330724
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 5:
+ time = 2353943
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 6:
+ time = 2377162
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 7:
+ time = 2400381
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 8:
+ time = 2423600
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 9:
+ time = 2446819
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 10:
+ time = 2470038
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 11:
+ time = 2493257
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 12:
+ time = 2516476
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 13:
+ time = 2539695
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 14:
+ time = 2562914
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 15:
+ time = 2586133
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 16:
+ time = 2609352
+ flags = 1
+ data = length 211, hash BA050052
+ sample 17:
+ time = 2632571
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 18:
+ time = 2655790
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 19:
+ time = 2679009
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 20:
+ time = 2702228
+ flags = 1
+ data = length 220, hash 32087455
+ sample 21:
+ time = 2725447
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 22:
+ time = 2748666
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 23:
+ time = 2771885
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 24:
+ time = 2795104
+ flags = 1
+ data = length 219, hash 45382270
+ sample 25:
+ time = 2818323
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 26:
+ time = 2841542
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 27:
+ time = 2864761
+ flags = 1
+ data = length 211, hash F0435574
+ sample 28:
+ time = 2887980
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 29:
+ time = 2911199
+ flags = 1
+ data = length 200, hash 82553248
+ sample 30:
+ time = 2934418
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 31:
+ time = 2957637
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 32:
+ time = 2980856
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 33:
+ time = 3004075
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 34:
+ time = 3027294
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 35:
+ time = 3050513
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 36:
+ time = 3073732
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 37:
+ time = 3096951
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 38:
+ time = 3120170
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 39:
+ time = 3143389
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 40:
+ time = 3166608
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 41:
+ time = 3189827
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 42:
+ time = 3213046
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 43:
+ time = 3236265
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 44:
+ time = 3259484
+ flags = 1
+ data = length 161, hash E991035D
+ sample 45:
+ time = 3282703
+ flags = 1
+ data = length 197, hash C3013689
+ sample 46:
+ time = 3305922
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 47:
+ time = 3329141
+ flags = 1
+ data = length 232, hash A330F188
+ sample 48:
+ time = 3352360
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs.adts.3.dump b/tree/testdata/src/test/assets/ts/sample_cbs.adts.3.dump
new file mode 100644
index 0000000..ae07524
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs.adts.3.dump
@@ -0,0 +1,30 @@
+seekMap:
+ isSeekable = true
+ duration = 3356772
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1678386) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3356772) = [[timeUs=3333553, position=31585]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 174
+ sample count = 1
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 3356772
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs.adts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_cbs.adts.unknown_length.dump
new file mode 100644
index 0000000..ddfea3e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs.adts.unknown_length.dump
@@ -0,0 +1,599 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample_cbs_truncated.adts
rename to tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.0.dump b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.0.dump
new file mode 100644
index 0000000..b94049c
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.0.dump
@@ -0,0 +1,598 @@
+seekMap:
+ isSeekable = true
+ duration = 3355717
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1677858) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3355717) = [[timeUs=3332497, position=31575]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30787
+ sample count = 143
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.1.dump b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.1.dump
new file mode 100644
index 0000000..79805d7
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.1.dump
@@ -0,0 +1,398 @@
+seekMap:
+ isSeekable = true
+ duration = 3355717
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1677858) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3355717) = [[timeUs=3332497, position=31575]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 20523
+ sample count = 93
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 1118572
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 1:
+ time = 1141791
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 2:
+ time = 1165010
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 3:
+ time = 1188229
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 4:
+ time = 1211448
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 5:
+ time = 1234667
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 6:
+ time = 1257886
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 7:
+ time = 1281105
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 8:
+ time = 1304324
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 9:
+ time = 1327543
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 10:
+ time = 1350762
+ flags = 1
+ data = length 212, hash 63214325
+ sample 11:
+ time = 1373981
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 12:
+ time = 1397200
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 13:
+ time = 1420419
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 14:
+ time = 1443638
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 15:
+ time = 1466857
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 16:
+ time = 1490076
+ flags = 1
+ data = length 195, hash A816647A
+ sample 17:
+ time = 1513295
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 18:
+ time = 1536514
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 19:
+ time = 1559733
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 20:
+ time = 1582952
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 21:
+ time = 1606171
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 22:
+ time = 1629390
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 23:
+ time = 1652609
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 24:
+ time = 1675828
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 25:
+ time = 1699047
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 26:
+ time = 1722266
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 27:
+ time = 1745485
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 28:
+ time = 1768704
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 29:
+ time = 1791923
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 30:
+ time = 1815142
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 31:
+ time = 1838361
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 32:
+ time = 1861580
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 33:
+ time = 1884799
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 34:
+ time = 1908018
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 35:
+ time = 1931237
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 36:
+ time = 1954456
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 37:
+ time = 1977675
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 38:
+ time = 2000894
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 39:
+ time = 2024113
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 40:
+ time = 2047332
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 41:
+ time = 2070551
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 42:
+ time = 2093770
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 43:
+ time = 2116989
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 44:
+ time = 2140208
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 45:
+ time = 2163427
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 46:
+ time = 2186646
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 47:
+ time = 2209865
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 48:
+ time = 2233084
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 49:
+ time = 2256303
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 50:
+ time = 2279522
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 51:
+ time = 2302741
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 52:
+ time = 2325960
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 53:
+ time = 2349179
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 54:
+ time = 2372398
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 55:
+ time = 2395617
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 56:
+ time = 2418836
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 57:
+ time = 2442055
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 58:
+ time = 2465274
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 59:
+ time = 2488493
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 60:
+ time = 2511712
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 61:
+ time = 2534931
+ flags = 1
+ data = length 211, hash BA050052
+ sample 62:
+ time = 2558150
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 63:
+ time = 2581369
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 64:
+ time = 2604588
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 65:
+ time = 2627807
+ flags = 1
+ data = length 220, hash 32087455
+ sample 66:
+ time = 2651026
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 67:
+ time = 2674245
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 68:
+ time = 2697464
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 69:
+ time = 2720683
+ flags = 1
+ data = length 219, hash 45382270
+ sample 70:
+ time = 2743902
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 71:
+ time = 2767121
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 72:
+ time = 2790340
+ flags = 1
+ data = length 211, hash F0435574
+ sample 73:
+ time = 2813559
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 74:
+ time = 2836778
+ flags = 1
+ data = length 200, hash 82553248
+ sample 75:
+ time = 2859997
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 76:
+ time = 2883216
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 77:
+ time = 2906435
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 78:
+ time = 2929654
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 79:
+ time = 2952873
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 80:
+ time = 2976092
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 81:
+ time = 2999311
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 82:
+ time = 3022530
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 83:
+ time = 3045749
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 84:
+ time = 3068968
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 85:
+ time = 3092187
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 86:
+ time = 3115406
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 87:
+ time = 3138625
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 88:
+ time = 3161844
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 89:
+ time = 3185063
+ flags = 1
+ data = length 161, hash E991035D
+ sample 90:
+ time = 3208282
+ flags = 1
+ data = length 197, hash C3013689
+ sample 91:
+ time = 3231501
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 92:
+ time = 3254720
+ flags = 1
+ data = length 232, hash A330F188
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.2.dump b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.2.dump
new file mode 100644
index 0000000..7c6278e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.2.dump
@@ -0,0 +1,218 @@
+seekMap:
+ isSeekable = true
+ duration = 3355717
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1677858) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3355717) = [[timeUs=3332497, position=31575]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 10151
+ sample count = 48
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 2237144
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 1:
+ time = 2260363
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 2:
+ time = 2283582
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 3:
+ time = 2306801
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 4:
+ time = 2330020
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 5:
+ time = 2353239
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 6:
+ time = 2376458
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 7:
+ time = 2399677
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 8:
+ time = 2422896
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 9:
+ time = 2446115
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 10:
+ time = 2469334
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 11:
+ time = 2492553
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 12:
+ time = 2515772
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 13:
+ time = 2538991
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 14:
+ time = 2562210
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 15:
+ time = 2585429
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 16:
+ time = 2608648
+ flags = 1
+ data = length 211, hash BA050052
+ sample 17:
+ time = 2631867
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 18:
+ time = 2655086
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 19:
+ time = 2678305
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 20:
+ time = 2701524
+ flags = 1
+ data = length 220, hash 32087455
+ sample 21:
+ time = 2724743
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 22:
+ time = 2747962
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 23:
+ time = 2771181
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 24:
+ time = 2794400
+ flags = 1
+ data = length 219, hash 45382270
+ sample 25:
+ time = 2817619
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 26:
+ time = 2840838
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 27:
+ time = 2864057
+ flags = 1
+ data = length 211, hash F0435574
+ sample 28:
+ time = 2887276
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 29:
+ time = 2910495
+ flags = 1
+ data = length 200, hash 82553248
+ sample 30:
+ time = 2933714
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 31:
+ time = 2956933
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 32:
+ time = 2980152
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 33:
+ time = 3003371
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 34:
+ time = 3026590
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 35:
+ time = 3049809
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 36:
+ time = 3073028
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 37:
+ time = 3096247
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 38:
+ time = 3119466
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 39:
+ time = 3142685
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 40:
+ time = 3165904
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 41:
+ time = 3189123
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 42:
+ time = 3212342
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 43:
+ time = 3235561
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 44:
+ time = 3258780
+ flags = 1
+ data = length 161, hash E991035D
+ sample 45:
+ time = 3281999
+ flags = 1
+ data = length 197, hash C3013689
+ sample 46:
+ time = 3305218
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 47:
+ time = 3328437
+ flags = 1
+ data = length 232, hash A330F188
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.3.dump b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.3.dump
new file mode 100644
index 0000000..518bcf4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.3.dump
@@ -0,0 +1,26 @@
+seekMap:
+ isSeekable = true
+ duration = 3355717
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=0, position=0], [timeUs=23219, position=220]]
+ getPosition(1677858) = [[timeUs=1671789, position=15840], [timeUs=1695009, position=16060]]
+ getPosition(3355717) = [[timeUs=3332497, position=31575]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 164
+ sample count = 0
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.unknown_length.dump
new file mode 100644
index 0000000..0af4459
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_cbs_truncated.adts.unknown_length.dump
@@ -0,0 +1,595 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30787
+ sample count = 143
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+track 1:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts b/tree/testdata/src/test/assets/ts/sample_eac3.ts
new file mode 100644
index 0000000..51d90ce
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_eac3.ts.0.dump
new file mode 100644
index 0000000..dfc89c5
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts.0.dump
@@ -0,0 +1,233 @@
+seekMap:
+ isSeekable = true
+ duration = 250077
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(125038) = [[timeUs=125038, position=109918]]
+ getPosition(250077) = [[timeUs=250077, position=220025]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 5333
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 10666
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 15999
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 21332
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 26665
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 32000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 37333
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 42666
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 47999
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 53332
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 58665
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 69333
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 74666
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 79999
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 85332
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 90665
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 101333
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 106666
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 111999
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 117332
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 122665
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 128000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 133333
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 138666
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 143999
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 149332
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 154665
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 165333
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 170666
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 175999
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 181332
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 186665
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 192000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 197333
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 202666
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 207999
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 213332
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 218665
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 224000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 229333
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 234666
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 239999
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 245332
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 250665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 256000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 261333
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 266666
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 271999
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 277332
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 282665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_eac3.ts.1.dump
new file mode 100644
index 0000000..c06294d
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts.1.dump
@@ -0,0 +1,185 @@
+seekMap:
+ isSeekable = true
+ duration = 250077
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(125038) = [[timeUs=125038, position=109918]]
+ getPosition(250077) = [[timeUs=250077, position=220025]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 168000
+ sample count = 42
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 1:
+ time = 69333
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 2:
+ time = 74666
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 3:
+ time = 79999
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 4:
+ time = 85332
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 5:
+ time = 90665
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 6:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 7:
+ time = 101333
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 8:
+ time = 106666
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 9:
+ time = 111999
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 10:
+ time = 117332
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 11:
+ time = 122665
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 12:
+ time = 128000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 13:
+ time = 133333
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 14:
+ time = 138666
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 15:
+ time = 143999
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 16:
+ time = 149332
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 17:
+ time = 154665
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 18:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 19:
+ time = 165333
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 20:
+ time = 170666
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 21:
+ time = 175999
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 22:
+ time = 181332
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 23:
+ time = 186665
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 24:
+ time = 192000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 25:
+ time = 197333
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 26:
+ time = 202666
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 27:
+ time = 207999
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 28:
+ time = 213332
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 29:
+ time = 218665
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 30:
+ time = 224000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 31:
+ time = 229333
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 32:
+ time = 234666
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 33:
+ time = 239999
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 34:
+ time = 245332
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 35:
+ time = 250665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 36:
+ time = 256000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 37:
+ time = 261333
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 38:
+ time = 266666
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 39:
+ time = 271999
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 40:
+ time = 277332
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 41:
+ time = 282665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_eac3.ts.2.dump
new file mode 100644
index 0000000..9104607
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts.2.dump
@@ -0,0 +1,113 @@
+seekMap:
+ isSeekable = true
+ duration = 250077
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(125038) = [[timeUs=125038, position=109918]]
+ getPosition(250077) = [[timeUs=250077, position=220025]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 96000
+ sample count = 24
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 1:
+ time = 165333
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 2:
+ time = 170666
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 3:
+ time = 175999
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 4:
+ time = 181332
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 5:
+ time = 186665
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 7:
+ time = 197333
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 8:
+ time = 202666
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 9:
+ time = 207999
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 10:
+ time = 213332
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 11:
+ time = 218665
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 12:
+ time = 224000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 13:
+ time = 229333
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 14:
+ time = 234666
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 15:
+ time = 239999
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 16:
+ time = 245332
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 17:
+ time = 250665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 18:
+ time = 256000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 19:
+ time = 261333
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 20:
+ time = 266666
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 21:
+ time = 271999
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 22:
+ time = 277332
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 23:
+ time = 282665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_eac3.ts.3.dump
new file mode 100644
index 0000000..c490b7e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts.3.dump
@@ -0,0 +1,17 @@
+seekMap:
+ isSeekable = true
+ duration = 250077
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(125038) = [[timeUs=125038, position=109918]]
+ getPosition(250077) = [[timeUs=250077, position=220025]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_eac3.ts.unknown_length.dump
new file mode 100644
index 0000000..0aae409
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3.ts.unknown_length.dump
@@ -0,0 +1,230 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 216000
+ sample count = 54
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 4000, hash BAEAFB2A
+ sample 1:
+ time = 5333
+ flags = 1
+ data = length 4000, hash E3C5EBF0
+ sample 2:
+ time = 10666
+ flags = 1
+ data = length 4000, hash 32E0F957
+ sample 3:
+ time = 15999
+ flags = 1
+ data = length 4000, hash 5354CC5D
+ sample 4:
+ time = 21332
+ flags = 1
+ data = length 4000, hash FF834906
+ sample 5:
+ time = 26665
+ flags = 1
+ data = length 4000, hash 6F571E61
+ sample 6:
+ time = 32000
+ flags = 1
+ data = length 4000, hash 5C931F6B
+ sample 7:
+ time = 37333
+ flags = 1
+ data = length 4000, hash B1FB2E57
+ sample 8:
+ time = 42666
+ flags = 1
+ data = length 4000, hash C71240EB
+ sample 9:
+ time = 47999
+ flags = 1
+ data = length 4000, hash C3E302EE
+ sample 10:
+ time = 53332
+ flags = 1
+ data = length 4000, hash 7994C27B
+ sample 11:
+ time = 58665
+ flags = 1
+ data = length 4000, hash 1ED4E6F3
+ sample 12:
+ time = 64000
+ flags = 1
+ data = length 4000, hash 1D5E6AAC
+ sample 13:
+ time = 69333
+ flags = 1
+ data = length 4000, hash 30058F51
+ sample 14:
+ time = 74666
+ flags = 1
+ data = length 4000, hash 15DD0E4A
+ sample 15:
+ time = 79999
+ flags = 1
+ data = length 4000, hash 37BE7C15
+ sample 16:
+ time = 85332
+ flags = 1
+ data = length 4000, hash 7CFDD34B
+ sample 17:
+ time = 90665
+ flags = 1
+ data = length 4000, hash 27F20D29
+ sample 18:
+ time = 96000
+ flags = 1
+ data = length 4000, hash 6F565894
+ sample 19:
+ time = 101333
+ flags = 1
+ data = length 4000, hash A6F07C4A
+ sample 20:
+ time = 106666
+ flags = 1
+ data = length 4000, hash 3A0CA15C
+ sample 21:
+ time = 111999
+ flags = 1
+ data = length 4000, hash DB365414
+ sample 22:
+ time = 117332
+ flags = 1
+ data = length 4000, hash 31E08469
+ sample 23:
+ time = 122665
+ flags = 1
+ data = length 4000, hash 315F5C28
+ sample 24:
+ time = 128000
+ flags = 1
+ data = length 4000, hash CC65DF80
+ sample 25:
+ time = 133333
+ flags = 1
+ data = length 4000, hash 503FB64C
+ sample 26:
+ time = 138666
+ flags = 1
+ data = length 4000, hash 817CF735
+ sample 27:
+ time = 143999
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 28:
+ time = 149332
+ flags = 1
+ data = length 4000, hash 37391ADA
+ sample 29:
+ time = 154665
+ flags = 1
+ data = length 4000, hash 64DBF751
+ sample 30:
+ time = 160000
+ flags = 1
+ data = length 4000, hash 81AE828E
+ sample 31:
+ time = 165333
+ flags = 1
+ data = length 4000, hash 767D6C98
+ sample 32:
+ time = 170666
+ flags = 1
+ data = length 4000, hash A5F6D4E
+ sample 33:
+ time = 175999
+ flags = 1
+ data = length 4000, hash EABC6B0D
+ sample 34:
+ time = 181332
+ flags = 1
+ data = length 4000, hash F47EF742
+ sample 35:
+ time = 186665
+ flags = 1
+ data = length 4000, hash 9B2549DA
+ sample 36:
+ time = 192000
+ flags = 1
+ data = length 4000, hash A12733C9
+ sample 37:
+ time = 197333
+ flags = 1
+ data = length 4000, hash 95F62E99
+ sample 38:
+ time = 202666
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 39:
+ time = 207999
+ flags = 1
+ data = length 4000, hash A4D858
+ sample 40:
+ time = 213332
+ flags = 1
+ data = length 4000, hash 22C1A129
+ sample 41:
+ time = 218665
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 42:
+ time = 224000
+ flags = 1
+ data = length 4000, hash 3782E8BB
+ sample 43:
+ time = 229333
+ flags = 1
+ data = length 4000, hash 2C51E4A1
+ sample 44:
+ time = 234666
+ flags = 1
+ data = length 4000, hash BDB3D129
+ sample 45:
+ time = 239999
+ flags = 1
+ data = length 4000, hash F642A55
+ sample 46:
+ time = 245332
+ flags = 1
+ data = length 4000, hash 32F259F4
+ sample 47:
+ time = 250665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 48:
+ time = 256000
+ flags = 1
+ data = length 4000, hash 57C98E1C
+ sample 49:
+ time = 261333
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 50:
+ time = 266666
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 51:
+ time = 271999
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 52:
+ time = 277332
+ flags = 1
+ data = length 4000, hash 4C987B7C
+ sample 53:
+ time = 282665
+ flags = 1
+ data = length 4000, hash 4C987B7C
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3 b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3
new file mode 100644
index 0000000..26ba6b4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.0.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.0.dump
new file mode 100644
index 0000000..f888869
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.0.dump
@@ -0,0 +1,270 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 0
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.unknown_length.dump
new file mode 100644
index 0000000..f888869
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ec3.unknown_length.dump
@@ -0,0 +1,270 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 0
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts
new file mode 100644
index 0000000..04bcf59
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.0.dump
new file mode 100644
index 0000000..a3cf812
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.0.dump
@@ -0,0 +1,273 @@
+seekMap:
+ isSeekable = true
+ duration = 2013977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(1006988) = [[timeUs=1006988, position=101524]]
+ getPosition(2013977) = [[timeUs=2013977, position=203237]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.1.dump
new file mode 100644
index 0000000..77951bd
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.1.dump
@@ -0,0 +1,193 @@
+seekMap:
+ isSeekable = true
+ duration = 2013977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(1006988) = [[timeUs=1006988, position=101524]]
+ getPosition(2013977) = [[timeUs=2013977, position=203237]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 112640
+ sample count = 44
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 1:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 2:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 3:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 4:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 5:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 6:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 7:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 8:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 9:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 10:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 11:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 12:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 13:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 14:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 15:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 16:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 17:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 18:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 19:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 20:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 21:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 22:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 23:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 24:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 25:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 26:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 27:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 28:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 29:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 30:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 31:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 32:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 33:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 34:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 35:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 36:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 37:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 38:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 39:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 40:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 41:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 42:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 43:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.2.dump
new file mode 100644
index 0000000..0354754
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.2.dump
@@ -0,0 +1,105 @@
+seekMap:
+ isSeekable = true
+ duration = 2013977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(1006988) = [[timeUs=1006988, position=101524]]
+ getPosition(2013977) = [[timeUs=2013977, position=203237]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 56320
+ sample count = 22
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 1:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 2:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 3:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 4:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 5:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 6:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 7:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 8:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 9:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 10:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 11:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 12:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 13:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 14:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 15:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 16:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 17:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 18:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 19:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 20:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 21:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.3.dump
new file mode 100644
index 0000000..742d87e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.3.dump
@@ -0,0 +1,25 @@
+seekMap:
+ isSeekable = true
+ duration = 2013977
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(1006988) = [[timeUs=1006988, position=101524]]
+ getPosition(2013977) = [[timeUs=2013977, position=203237]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 5120
+ sample count = 2
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 1:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.unknown_length.dump
new file mode 100644
index 0000000..269dd63
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_eac3joc.ts.unknown_length.dump
@@ -0,0 +1,270 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 1900:
+ total output bytes = 163840
+ sample count = 64
+ format 0:
+ id = 1/1900
+ sampleMimeType = audio/eac3-joc
+ channelCount = 6
+ sampleRate = 48000
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 2560, hash 882594AD
+ sample 1:
+ time = 32000
+ flags = 1
+ data = length 2560, hash 41EC8B22
+ sample 2:
+ time = 64000
+ flags = 1
+ data = length 2560, hash 67E6EFD4
+ sample 3:
+ time = 96000
+ flags = 1
+ data = length 2560, hash A7E66AFD
+ sample 4:
+ time = 128000
+ flags = 1
+ data = length 2560, hash 3924116
+ sample 5:
+ time = 160000
+ flags = 1
+ data = length 2560, hash 64DCE40B
+ sample 6:
+ time = 192000
+ flags = 1
+ data = length 2560, hash F2E0DA64
+ sample 7:
+ time = 224000
+ flags = 1
+ data = length 2560, hash C156258B
+ sample 8:
+ time = 256000
+ flags = 1
+ data = length 2560, hash D8DBDCDE
+ sample 9:
+ time = 288000
+ flags = 1
+ data = length 2560, hash C11B2F25
+ sample 10:
+ time = 320000
+ flags = 1
+ data = length 2560, hash B3C5612
+ sample 11:
+ time = 352000
+ flags = 1
+ data = length 2560, hash A94B15D0
+ sample 12:
+ time = 384000
+ flags = 1
+ data = length 2560, hash 12E4E306
+ sample 13:
+ time = 416000
+ flags = 1
+ data = length 2560, hash 11CB959F
+ sample 14:
+ time = 448000
+ flags = 1
+ data = length 2560, hash B6433844
+ sample 15:
+ time = 480000
+ flags = 1
+ data = length 2560, hash EA6DEB89
+ sample 16:
+ time = 512000
+ flags = 1
+ data = length 2560, hash 6D65CBD9
+ sample 17:
+ time = 544000
+ flags = 1
+ data = length 2560, hash A5D635C5
+ sample 18:
+ time = 576000
+ flags = 1
+ data = length 2560, hash 992E36AB
+ sample 19:
+ time = 608000
+ flags = 1
+ data = length 2560, hash 1EC4E5AF
+ sample 20:
+ time = 640000
+ flags = 1
+ data = length 2560, hash DCFEB7D2
+ sample 21:
+ time = 672000
+ flags = 1
+ data = length 2560, hash 45EFC639
+ sample 22:
+ time = 704000
+ flags = 1
+ data = length 2560, hash F598673
+ sample 23:
+ time = 736000
+ flags = 1
+ data = length 2560, hash 89E4E5EC
+ sample 24:
+ time = 768000
+ flags = 1
+ data = length 2560, hash FBE2532B
+ sample 25:
+ time = 800000
+ flags = 1
+ data = length 2560, hash 9CE5F83B
+ sample 26:
+ time = 832000
+ flags = 1
+ data = length 2560, hash 6ED49E2C
+ sample 27:
+ time = 864000
+ flags = 1
+ data = length 2560, hash BC52F8F3
+ sample 28:
+ time = 896000
+ flags = 1
+ data = length 2560, hash 759203E2
+ sample 29:
+ time = 928000
+ flags = 1
+ data = length 2560, hash D5D31AE9
+ sample 30:
+ time = 960000
+ flags = 1
+ data = length 2560, hash 640A24ED
+ sample 31:
+ time = 992000
+ flags = 1
+ data = length 2560, hash 19B52B8B
+ sample 32:
+ time = 1024000
+ flags = 1
+ data = length 2560, hash 5DA977C3
+ sample 33:
+ time = 1056000
+ flags = 1
+ data = length 2560, hash 982879DD
+ sample 34:
+ time = 1088000
+ flags = 1
+ data = length 2560, hash A7656B9C
+ sample 35:
+ time = 1120000
+ flags = 1
+ data = length 2560, hash 445CCC67
+ sample 36:
+ time = 1152000
+ flags = 1
+ data = length 2560, hash ACD5CB5C
+ sample 37:
+ time = 1184000
+ flags = 1
+ data = length 2560, hash 175BBF26
+ sample 38:
+ time = 1216000
+ flags = 1
+ data = length 2560, hash DBCBEB0
+ sample 39:
+ time = 1248000
+ flags = 1
+ data = length 2560, hash DA39D991
+ sample 40:
+ time = 1280000
+ flags = 1
+ data = length 2560, hash F08CC8E2
+ sample 41:
+ time = 1312000
+ flags = 1
+ data = length 2560, hash 6B0842D7
+ sample 42:
+ time = 1344000
+ flags = 1
+ data = length 2560, hash 9FE87594
+ sample 43:
+ time = 1376000
+ flags = 1
+ data = length 2560, hash 8E62CE19
+ sample 44:
+ time = 1408000
+ flags = 1
+ data = length 2560, hash 5FDC4084
+ sample 45:
+ time = 1440000
+ flags = 1
+ data = length 2560, hash C32DAEE1
+ sample 46:
+ time = 1472000
+ flags = 1
+ data = length 2560, hash BBEFB568
+ sample 47:
+ time = 1504000
+ flags = 1
+ data = length 2560, hash 20504279
+ sample 48:
+ time = 1536000
+ flags = 1
+ data = length 2560, hash 3B8192D2
+ sample 49:
+ time = 1568000
+ flags = 1
+ data = length 2560, hash 4206B48
+ sample 50:
+ time = 1600000
+ flags = 1
+ data = length 2560, hash B195AB53
+ sample 51:
+ time = 1632000
+ flags = 1
+ data = length 2560, hash 3AA8E25F
+ sample 52:
+ time = 1664000
+ flags = 1
+ data = length 2560, hash BC227D7B
+ sample 53:
+ time = 1696000
+ flags = 1
+ data = length 2560, hash 6A34F7EA
+ sample 54:
+ time = 1728000
+ flags = 1
+ data = length 2560, hash F1E731C4
+ sample 55:
+ time = 1760000
+ flags = 1
+ data = length 2560, hash 9CC406
+ sample 56:
+ time = 1792000
+ flags = 1
+ data = length 2560, hash A1532233
+ sample 57:
+ time = 1824000
+ flags = 1
+ data = length 2560, hash 98E49039
+ sample 58:
+ time = 1856000
+ flags = 1
+ data = length 2560, hash 3F8B6DC0
+ sample 59:
+ time = 1888000
+ flags = 1
+ data = length 2560, hash 4E7BF79F
+ sample 60:
+ time = 1920000
+ flags = 1
+ data = length 2560, hash 6DD6F2D7
+ sample 61:
+ time = 1952000
+ flags = 1
+ data = length 2560, hash A05C0EC2
+ sample 62:
+ time = 1984000
+ flags = 1
+ data = length 2560, hash 10C62F30
+ sample 63:
+ time = 2016000
+ flags = 1
+ data = length 2560, hash EE4F848A
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ps b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.ps
rename to tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.0.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.0.dump
new file mode 100644
index 0000000..d623516
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.0.dump
@@ -0,0 +1,52 @@
+seekMap:
+ isSeekable = true
+ duration = 766
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(383) = [[timeUs=383, position=23128]]
+ getPosition(766) = [[timeUs=766, position=46445]]
+numberOfTracks = 2
+track 192:
+ total output bytes = 1671
+ sample count = 4
+ format 0:
+ id = 192
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 29088
+ flags = 1
+ data = length 417, hash 5C710F78
+ sample 1:
+ time = 55210
+ flags = 1
+ data = length 418, hash 79CF71F8
+ sample 2:
+ time = 81332
+ flags = 1
+ data = length 418, hash 79CF71F8
+ sample 3:
+ time = 107454
+ flags = 1
+ data = length 418, hash 79CF71F8
+track 224:
+ total output bytes = 44056
+ sample count = 2
+ format 0:
+ id = 224
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash 743CC6F8
+ sample 0:
+ time = 40000
+ flags = 1
+ data = length 20646, hash 576390B
+ sample 1:
+ time = 80000
+ flags = 0
+ data = length 17831, hash 5C5A57F5
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.1.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.1.dump
new file mode 100644
index 0000000..9bdd929
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.1.dump
@@ -0,0 +1,32 @@
+seekMap:
+ isSeekable = true
+ duration = 766
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(383) = [[timeUs=383, position=23128]]
+ getPosition(766) = [[timeUs=766, position=46445]]
+numberOfTracks = 2
+track 192:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 192
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+track 224:
+ total output bytes = 33949
+ sample count = 1
+ format 0:
+ id = 224
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash 743CC6F8
+ sample 0:
+ time = 80000
+ flags = 0
+ data = length 17831, hash 5C5A57F5
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.2.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.2.dump
new file mode 100644
index 0000000..ef5e247
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.2.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 766
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(383) = [[timeUs=383, position=23128]]
+ getPosition(766) = [[timeUs=766, position=46445]]
+numberOfTracks = 2
+track 192:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 192
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+track 224:
+ total output bytes = 19791
+ sample count = 0
+ format 0:
+ id = 224
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash 743CC6F8
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.3.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.3.dump
new file mode 100644
index 0000000..59dabce
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.3.dump
@@ -0,0 +1,28 @@
+seekMap:
+ isSeekable = true
+ duration = 766
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(383) = [[timeUs=383, position=23128]]
+ getPosition(766) = [[timeUs=766, position=46445]]
+numberOfTracks = 2
+track 192:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 192
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+track 224:
+ total output bytes = 1585
+ sample count = 0
+ format 0:
+ id = 224
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash 743CC6F8
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.unknown_length.dump
new file mode 100644
index 0000000..5edc0b7
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ps.unknown_length.dump
@@ -0,0 +1,49 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 192:
+ total output bytes = 1671
+ sample count = 4
+ format 0:
+ id = 192
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ sample 0:
+ time = 29088
+ flags = 1
+ data = length 417, hash 5C710F78
+ sample 1:
+ time = 55210
+ flags = 1
+ data = length 418, hash 79CF71F8
+ sample 2:
+ time = 81332
+ flags = 1
+ data = length 418, hash 79CF71F8
+ sample 3:
+ time = 107454
+ flags = 1
+ data = length 418, hash 79CF71F8
+track 224:
+ total output bytes = 44056
+ sample count = 2
+ format 0:
+ id = 224
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash 743CC6F8
+ sample 0:
+ time = 40000
+ flags = 1
+ data = length 20646, hash 576390B
+ sample 1:
+ time = 80000
+ flags = 0
+ data = length 17831, hash 5C5A57F5
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample.ts b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample.ts
rename to tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.0.dump
new file mode 100644
index 0000000..c7160e4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.0.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25709]]
+ getPosition(66733) = [[timeUs=66733, position=51606]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.1.dump
new file mode 100644
index 0000000..cf45b1f
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.1.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25709]]
+ getPosition(66733) = [[timeUs=66733, position=51606]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 55610
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 88977
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 44699
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 70821
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 96944
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 123066
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.2.dump
new file mode 100644
index 0000000..bc9786b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.2.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25709]]
+ getPosition(66733) = [[timeUs=66733, position=51606]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 77854
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 111221
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66943
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 93065
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 119188
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 145310
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.3.dump
new file mode 100644
index 0000000..727fa38
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.3.dump
@@ -0,0 +1,43 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25709]]
+ getPosition(66733) = [[timeUs=66733, position=51606]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+track 257:
+ total output bytes = 2508
+ sample count = 2
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 1:
+ time = 92855
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.unknown_length.dump
new file mode 100644
index 0000000..e18961d
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h262_mpeg_audio.ts.unknown_length.dump
@@ -0,0 +1,56 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts
new file mode 100644
index 0000000..dabf547
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.0.dump
new file mode 100644
index 0000000..49ec76e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.0.dump
@@ -0,0 +1,61 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=9545]]
+ getPosition(66733) = [[timeUs=66733, position=19279]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 13650
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/avc
+ codecs = avc1.64001E
+ width = 640
+ height = 426
+ initializationData:
+ data = length 29, hash 4C2CAE9C
+ data = length 9, hash D971CD89
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 12394, hash A39F5311
+ sample 1:
+ time = 100100
+ flags = 0
+ data = length 813, hash 99F7B4FA
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 92855
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 118977
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 145099
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.1.dump
new file mode 100644
index 0000000..ddd38b6
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.1.dump
@@ -0,0 +1,61 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=9545]]
+ getPosition(66733) = [[timeUs=66733, position=19279]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 13650
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/avc
+ codecs = avc1.64001E
+ width = 640
+ height = 426
+ initializationData:
+ data = length 29, hash 4C2CAE9C
+ data = length 9, hash D971CD89
+ sample 0:
+ time = 88977
+ flags = 1
+ data = length 12394, hash A39F5311
+ sample 1:
+ time = 122344
+ flags = 0
+ data = length 813, hash 99F7B4FA
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 88977
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 115099
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 141221
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 167343
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.2.dump
new file mode 100644
index 0000000..53290c3
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.2.dump
@@ -0,0 +1,61 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=9545]]
+ getPosition(66733) = [[timeUs=66733, position=19279]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 13650
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/avc
+ codecs = avc1.64001E
+ width = 640
+ height = 426
+ initializationData:
+ data = length 29, hash 4C2CAE9C
+ data = length 9, hash D971CD89
+ sample 0:
+ time = 111221
+ flags = 1
+ data = length 12394, hash A39F5311
+ sample 1:
+ time = 144588
+ flags = 0
+ data = length 813, hash 99F7B4FA
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 111221
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 137343
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 163465
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 189587
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.3.dump
new file mode 100644
index 0000000..2be5c14
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.3.dump
@@ -0,0 +1,37 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=9545]]
+ getPosition(66733) = [[timeUs=66733, position=19279]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/256
+ sampleMimeType = video/avc
+ codecs = avc1.64001E
+ width = 640
+ height = 426
+ initializationData:
+ data = length 29, hash 4C2CAE9C
+ data = length 9, hash D971CD89
+track 257:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.unknown_length.dump
new file mode 100644
index 0000000..86327e7
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h264_mpeg_audio.ts.unknown_length.dump
@@ -0,0 +1,58 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 13650
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/avc
+ codecs = avc1.64001E
+ width = 640
+ height = 426
+ initializationData:
+ data = length 29, hash 4C2CAE9C
+ data = length 9, hash D971CD89
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 12394, hash A39F5311
+ sample 1:
+ time = 100100
+ flags = 0
+ data = length 813, hash 99F7B4FA
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 92855
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 118977
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 145099
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts b/tree/testdata/src/test/assets/ts/sample_latm.ts
new file mode 100644
index 0000000..a6a6e0b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_latm.ts.0.dump
new file mode 100644
index 0000000..51e3ce4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts.0.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 256:
+ total output bytes = 1396
+ sample count = 5
+ format 0:
+ id = 1/256
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.5
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 4, hash 1FE978
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 279, hash 79BF9F9B
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 279, hash C96F4684
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 279, hash 65670B86
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 280, hash 1AF29BCE
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 279, hash C96F4684
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_latm.ts.1.dump
new file mode 100644
index 0000000..51e3ce4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts.1.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 256:
+ total output bytes = 1396
+ sample count = 5
+ format 0:
+ id = 1/256
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.5
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 4, hash 1FE978
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 279, hash 79BF9F9B
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 279, hash C96F4684
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 279, hash 65670B86
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 280, hash 1AF29BCE
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 279, hash C96F4684
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_latm.ts.2.dump
new file mode 100644
index 0000000..51e3ce4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts.2.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 256:
+ total output bytes = 1396
+ sample count = 5
+ format 0:
+ id = 1/256
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.5
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 4, hash 1FE978
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 279, hash 79BF9F9B
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 279, hash C96F4684
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 279, hash 65670B86
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 280, hash 1AF29BCE
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 279, hash C96F4684
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_latm.ts.3.dump
new file mode 100644
index 0000000..51e3ce4
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts.3.dump
@@ -0,0 +1,41 @@
+seekMap:
+ isSeekable = true
+ duration = 0
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 256:
+ total output bytes = 1396
+ sample count = 5
+ format 0:
+ id = 1/256
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.5
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 4, hash 1FE978
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 279, hash 79BF9F9B
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 279, hash C96F4684
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 279, hash 65670B86
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 280, hash 1AF29BCE
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 279, hash C96F4684
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_latm.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_latm.ts.unknown_length.dump
new file mode 100644
index 0000000..e97431e
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_latm.ts.unknown_length.dump
@@ -0,0 +1,38 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 1
+track 256:
+ total output bytes = 1396
+ sample count = 5
+ format 0:
+ id = 1/256
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.5
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ initializationData:
+ data = length 4, hash 1FE978
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 279, hash 79BF9F9B
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 279, hash C96F4684
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 279, hash 65670B86
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 280, hash 1AF29BCE
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 279, hash C96F4684
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts b/tree/testdata/src/test/assets/ts/sample_scte35.ts
new file mode 100644
index 0000000..81f9ce9
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_scte35.ts.0.dump
new file mode 100644
index 0000000..b545d60
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts.0.dump
@@ -0,0 +1,77 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25887]]
+ getPosition(66733) = [[timeUs=66733, position=51963]]
+numberOfTracks = 4
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 600:
+ total output bytes = 105
+ sample count = 3
+ format 0:
+ sampleMimeType = application/x-scte35
+ subsampleOffsetUs = -1400000
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 1:
+ time = 33366
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 2:
+ time = 33366
+ flags = 1
+ data = length 35, hash DFA3EF74
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_scte35.ts.1.dump
new file mode 100644
index 0000000..95f905c
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts.1.dump
@@ -0,0 +1,77 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25887]]
+ getPosition(66733) = [[timeUs=66733, position=51963]]
+numberOfTracks = 4
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 55610
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 88977
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 44699
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 70821
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 96944
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 123066
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 600:
+ total output bytes = 105
+ sample count = 3
+ format 0:
+ sampleMimeType = application/x-scte35
+ subsampleOffsetUs = -1400000
+ sample 0:
+ time = 55610
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 1:
+ time = 55610
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 2:
+ time = 55610
+ flags = 1
+ data = length 35, hash DFA3EF74
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_scte35.ts.2.dump
new file mode 100644
index 0000000..b7ffabc
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts.2.dump
@@ -0,0 +1,77 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25887]]
+ getPosition(66733) = [[timeUs=66733, position=51963]]
+numberOfTracks = 4
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 77854
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 111221
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66943
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 93065
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 119188
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 145310
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 600:
+ total output bytes = 105
+ sample count = 3
+ format 0:
+ sampleMimeType = application/x-scte35
+ subsampleOffsetUs = -1400000
+ sample 0:
+ time = 77854
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 1:
+ time = 77854
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 2:
+ time = 77854
+ flags = 1
+ data = length 35, hash DFA3EF74
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_scte35.ts.3.dump
new file mode 100644
index 0000000..660ee8b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts.3.dump
@@ -0,0 +1,49 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=25887]]
+ getPosition(66733) = [[timeUs=66733, position=51963]]
+numberOfTracks = 4
+track 256:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+track 257:
+ total output bytes = 2508
+ sample count = 2
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 1:
+ time = 92855
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 600:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = application/x-scte35
+ subsampleOffsetUs = -1400000
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_scte35.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_scte35.ts.unknown_length.dump
new file mode 100644
index 0000000..d3e8c32
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_scte35.ts.unknown_length.dump
@@ -0,0 +1,74 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 4
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 600:
+ total output bytes = 105
+ sample count = 3
+ format 0:
+ sampleMimeType = application/x-scte35
+ subsampleOffsetUs = -1400000
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 1:
+ time = 33366
+ flags = 1
+ data = length 35, hash A892AAAF
+ sample 2:
+ time = 33366
+ flags = 1
+ data = length 35, hash DFA3EF74
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_id3.adts b/tree/testdata/src/test/assets/ts/sample_with_id3.adts
new file mode 100644
index 0000000..690fe90
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_id3.adts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_with_id3.adts.0.dump b/tree/testdata/src/test/assets/ts/sample_with_id3.adts.0.dump
new file mode 100644
index 0000000..1f5d510
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_id3.adts.0.dump
@@ -0,0 +1,607 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 141
+ sample count = 2
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 55, hash A7EB51A0
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 86, hash 3FA72D40
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_id3.adts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_with_id3.adts.unknown_length.dump
new file mode 100644
index 0000000..1f5d510
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_id3.adts.unknown_length.dump
@@ -0,0 +1,607 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 0:
+ total output bytes = 30797
+ sample count = 144
+ format 0:
+ id = 0
+ sampleMimeType = audio/mp4a-latm
+ codecs = mp4a.40.2
+ channelCount = 1
+ sampleRate = 44100
+ initializationData:
+ data = length 2, hash 5F7
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 23, hash 47DE9131
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 2:
+ time = 46438
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 3:
+ time = 69657
+ flags = 1
+ data = length 6, hash 31CF3A46
+ sample 4:
+ time = 92876
+ flags = 1
+ data = length 6, hash 31EC5206
+ sample 5:
+ time = 116095
+ flags = 1
+ data = length 171, hash 4F6478F6
+ sample 6:
+ time = 139314
+ flags = 1
+ data = length 202, hash AF4068A3
+ sample 7:
+ time = 162533
+ flags = 1
+ data = length 210, hash E4C10618
+ sample 8:
+ time = 185752
+ flags = 1
+ data = length 217, hash 9ECCD0D9
+ sample 9:
+ time = 208971
+ flags = 1
+ data = length 212, hash 6BAC2CD9
+ sample 10:
+ time = 232190
+ flags = 1
+ data = length 223, hash 188B6010
+ sample 11:
+ time = 255409
+ flags = 1
+ data = length 222, hash C1A04D0C
+ sample 12:
+ time = 278628
+ flags = 1
+ data = length 220, hash D65F9768
+ sample 13:
+ time = 301847
+ flags = 1
+ data = length 227, hash B96C9E14
+ sample 14:
+ time = 325066
+ flags = 1
+ data = length 229, hash 9FB09972
+ sample 15:
+ time = 348285
+ flags = 1
+ data = length 220, hash 2271F053
+ sample 16:
+ time = 371504
+ flags = 1
+ data = length 226, hash 5EDD2F4F
+ sample 17:
+ time = 394723
+ flags = 1
+ data = length 239, hash 957510E0
+ sample 18:
+ time = 417942
+ flags = 1
+ data = length 224, hash 718A8F47
+ sample 19:
+ time = 441161
+ flags = 1
+ data = length 225, hash 5E11E293
+ sample 20:
+ time = 464380
+ flags = 1
+ data = length 227, hash FCE50D27
+ sample 21:
+ time = 487599
+ flags = 1
+ data = length 212, hash 77908C40
+ sample 22:
+ time = 510818
+ flags = 1
+ data = length 227, hash 34C4EB32
+ sample 23:
+ time = 534037
+ flags = 1
+ data = length 231, hash 95488307
+ sample 24:
+ time = 557256
+ flags = 1
+ data = length 226, hash 97F12D6F
+ sample 25:
+ time = 580475
+ flags = 1
+ data = length 236, hash 91A9D9A2
+ sample 26:
+ time = 603694
+ flags = 1
+ data = length 227, hash 27A608F9
+ sample 27:
+ time = 626913
+ flags = 1
+ data = length 229, hash 57DAAE4
+ sample 28:
+ time = 650132
+ flags = 1
+ data = length 235, hash ED30AC34
+ sample 29:
+ time = 673351
+ flags = 1
+ data = length 227, hash BD3D6280
+ sample 30:
+ time = 696570
+ flags = 1
+ data = length 233, hash 694B1087
+ sample 31:
+ time = 719789
+ flags = 1
+ data = length 232, hash 1EDFE047
+ sample 32:
+ time = 743008
+ flags = 1
+ data = length 228, hash E2A831F4
+ sample 33:
+ time = 766227
+ flags = 1
+ data = length 231, hash 757E6012
+ sample 34:
+ time = 789446
+ flags = 1
+ data = length 223, hash 4003D791
+ sample 35:
+ time = 812665
+ flags = 1
+ data = length 232, hash 3CF9A07C
+ sample 36:
+ time = 835884
+ flags = 1
+ data = length 228, hash 25AC3FF7
+ sample 37:
+ time = 859103
+ flags = 1
+ data = length 220, hash 2C1824CE
+ sample 38:
+ time = 882322
+ flags = 1
+ data = length 229, hash 46FDD8FB
+ sample 39:
+ time = 905541
+ flags = 1
+ data = length 237, hash F6988018
+ sample 40:
+ time = 928760
+ flags = 1
+ data = length 242, hash 60436B6B
+ sample 41:
+ time = 951979
+ flags = 1
+ data = length 275, hash 90EDFA8E
+ sample 42:
+ time = 975198
+ flags = 1
+ data = length 242, hash 5C86EFCB
+ sample 43:
+ time = 998417
+ flags = 1
+ data = length 233, hash E0A51B82
+ sample 44:
+ time = 1021636
+ flags = 1
+ data = length 235, hash 590DF14F
+ sample 45:
+ time = 1044855
+ flags = 1
+ data = length 238, hash 69AF4E6E
+ sample 46:
+ time = 1068074
+ flags = 1
+ data = length 235, hash E745AE8D
+ sample 47:
+ time = 1091293
+ flags = 1
+ data = length 223, hash 295F2A13
+ sample 48:
+ time = 1114512
+ flags = 1
+ data = length 228, hash E2F47B21
+ sample 49:
+ time = 1137731
+ flags = 1
+ data = length 229, hash 262C3CFE
+ sample 50:
+ time = 1160950
+ flags = 1
+ data = length 232, hash 4B5BF5E8
+ sample 51:
+ time = 1184169
+ flags = 1
+ data = length 233, hash F3D80836
+ sample 52:
+ time = 1207388
+ flags = 1
+ data = length 237, hash 32E0A11E
+ sample 53:
+ time = 1230607
+ flags = 1
+ data = length 228, hash E1B89F13
+ sample 54:
+ time = 1253826
+ flags = 1
+ data = length 237, hash 8BDD9E38
+ sample 55:
+ time = 1277045
+ flags = 1
+ data = length 235, hash 3C84161F
+ sample 56:
+ time = 1300264
+ flags = 1
+ data = length 227, hash A47E1789
+ sample 57:
+ time = 1323483
+ flags = 1
+ data = length 228, hash 869FDFD3
+ sample 58:
+ time = 1346702
+ flags = 1
+ data = length 233, hash 272ECE2
+ sample 59:
+ time = 1369921
+ flags = 1
+ data = length 227, hash DB6B9618
+ sample 60:
+ time = 1393140
+ flags = 1
+ data = length 212, hash 63214325
+ sample 61:
+ time = 1416359
+ flags = 1
+ data = length 221, hash 9BA588A1
+ sample 62:
+ time = 1439578
+ flags = 1
+ data = length 225, hash 21EFD50C
+ sample 63:
+ time = 1462797
+ flags = 1
+ data = length 231, hash F3AD0BF
+ sample 64:
+ time = 1486016
+ flags = 1
+ data = length 224, hash 822C9210
+ sample 65:
+ time = 1509235
+ flags = 1
+ data = length 195, hash D4EF53EE
+ sample 66:
+ time = 1532454
+ flags = 1
+ data = length 195, hash A816647A
+ sample 67:
+ time = 1555673
+ flags = 1
+ data = length 184, hash 9A2B7E6
+ sample 68:
+ time = 1578892
+ flags = 1
+ data = length 210, hash 956E3600
+ sample 69:
+ time = 1602111
+ flags = 1
+ data = length 234, hash 35CFDA0A
+ sample 70:
+ time = 1625330
+ flags = 1
+ data = length 239, hash 9E15AC1E
+ sample 71:
+ time = 1648549
+ flags = 1
+ data = length 228, hash F3B70641
+ sample 72:
+ time = 1671768
+ flags = 1
+ data = length 237, hash 124E3194
+ sample 73:
+ time = 1694987
+ flags = 1
+ data = length 231, hash 950CD7C8
+ sample 74:
+ time = 1718206
+ flags = 1
+ data = length 236, hash A12E49AF
+ sample 75:
+ time = 1741425
+ flags = 1
+ data = length 242, hash 43BC9C24
+ sample 76:
+ time = 1764644
+ flags = 1
+ data = length 241, hash DCF0B17
+ sample 77:
+ time = 1787863
+ flags = 1
+ data = length 251, hash C0B99968
+ sample 78:
+ time = 1811082
+ flags = 1
+ data = length 245, hash 9B38ED1C
+ sample 79:
+ time = 1834301
+ flags = 1
+ data = length 238, hash 1BA69079
+ sample 80:
+ time = 1857520
+ flags = 1
+ data = length 233, hash 44C8C6BF
+ sample 81:
+ time = 1880739
+ flags = 1
+ data = length 231, hash EABBEE02
+ sample 82:
+ time = 1903958
+ flags = 1
+ data = length 226, hash D09C44FB
+ sample 83:
+ time = 1927177
+ flags = 1
+ data = length 235, hash BE6A6608
+ sample 84:
+ time = 1950396
+ flags = 1
+ data = length 235, hash 2735F454
+ sample 85:
+ time = 1973615
+ flags = 1
+ data = length 238, hash B160DFE7
+ sample 86:
+ time = 1996834
+ flags = 1
+ data = length 232, hash 1B217D2E
+ sample 87:
+ time = 2020053
+ flags = 1
+ data = length 251, hash D1C14CEA
+ sample 88:
+ time = 2043272
+ flags = 1
+ data = length 256, hash 97C87F08
+ sample 89:
+ time = 2066491
+ flags = 1
+ data = length 237, hash 6645DB3
+ sample 90:
+ time = 2089710
+ flags = 1
+ data = length 235, hash 727A1C82
+ sample 91:
+ time = 2112929
+ flags = 1
+ data = length 234, hash 5015F8B5
+ sample 92:
+ time = 2136148
+ flags = 1
+ data = length 241, hash 9102144B
+ sample 93:
+ time = 2159367
+ flags = 1
+ data = length 224, hash 64E0D807
+ sample 94:
+ time = 2182586
+ flags = 1
+ data = length 228, hash 1922B852
+ sample 95:
+ time = 2205805
+ flags = 1
+ data = length 224, hash 953502D8
+ sample 96:
+ time = 2229024
+ flags = 1
+ data = length 214, hash 92B87FE7
+ sample 97:
+ time = 2252243
+ flags = 1
+ data = length 213, hash BB0C8D86
+ sample 98:
+ time = 2275462
+ flags = 1
+ data = length 206, hash 9AD21017
+ sample 99:
+ time = 2298681
+ flags = 1
+ data = length 209, hash C479FE94
+ sample 100:
+ time = 2321900
+ flags = 1
+ data = length 220, hash 3033DCE1
+ sample 101:
+ time = 2345119
+ flags = 1
+ data = length 217, hash 7D589C94
+ sample 102:
+ time = 2368338
+ flags = 1
+ data = length 216, hash AAF6C183
+ sample 103:
+ time = 2391557
+ flags = 1
+ data = length 206, hash 1EE1207F
+ sample 104:
+ time = 2414776
+ flags = 1
+ data = length 204, hash 4BEB1210
+ sample 105:
+ time = 2437995
+ flags = 1
+ data = length 213, hash 21A841C9
+ sample 106:
+ time = 2461214
+ flags = 1
+ data = length 207, hash B80B0424
+ sample 107:
+ time = 2484433
+ flags = 1
+ data = length 212, hash 4785A1C3
+ sample 108:
+ time = 2507652
+ flags = 1
+ data = length 205, hash 59BF7229
+ sample 109:
+ time = 2530871
+ flags = 1
+ data = length 208, hash FA313DDE
+ sample 110:
+ time = 2554090
+ flags = 1
+ data = length 211, hash 190D85FD
+ sample 111:
+ time = 2577309
+ flags = 1
+ data = length 211, hash BA050052
+ sample 112:
+ time = 2600528
+ flags = 1
+ data = length 211, hash F3080F10
+ sample 113:
+ time = 2623747
+ flags = 1
+ data = length 210, hash F41B7BE7
+ sample 114:
+ time = 2646966
+ flags = 1
+ data = length 207, hash 2176C97E
+ sample 115:
+ time = 2670185
+ flags = 1
+ data = length 220, hash 32087455
+ sample 116:
+ time = 2693404
+ flags = 1
+ data = length 213, hash 4E5649A8
+ sample 117:
+ time = 2716623
+ flags = 1
+ data = length 213, hash 5F12FDCF
+ sample 118:
+ time = 2739842
+ flags = 1
+ data = length 204, hash 1E895C2A
+ sample 119:
+ time = 2763061
+ flags = 1
+ data = length 219, hash 45382270
+ sample 120:
+ time = 2786280
+ flags = 1
+ data = length 205, hash D66C6A1D
+ sample 121:
+ time = 2809499
+ flags = 1
+ data = length 204, hash 467AD01F
+ sample 122:
+ time = 2832718
+ flags = 1
+ data = length 211, hash F0435574
+ sample 123:
+ time = 2855937
+ flags = 1
+ data = length 206, hash 8C96B75F
+ sample 124:
+ time = 2879156
+ flags = 1
+ data = length 200, hash 82553248
+ sample 125:
+ time = 2902375
+ flags = 1
+ data = length 180, hash 1E51E6CE
+ sample 126:
+ time = 2925594
+ flags = 1
+ data = length 196, hash 33151DC4
+ sample 127:
+ time = 2948813
+ flags = 1
+ data = length 197, hash 1E62A7D6
+ sample 128:
+ time = 2972032
+ flags = 1
+ data = length 206, hash 6A6C4CC9
+ sample 129:
+ time = 2995251
+ flags = 1
+ data = length 209, hash A72FABAA
+ sample 130:
+ time = 3018470
+ flags = 1
+ data = length 217, hash BA33B985
+ sample 131:
+ time = 3041689
+ flags = 1
+ data = length 235, hash 9919CFD9
+ sample 132:
+ time = 3064908
+ flags = 1
+ data = length 236, hash A22C7267
+ sample 133:
+ time = 3088127
+ flags = 1
+ data = length 213, hash 3D57C901
+ sample 134:
+ time = 3111346
+ flags = 1
+ data = length 205, hash 47F68FDE
+ sample 135:
+ time = 3134565
+ flags = 1
+ data = length 210, hash 9A756E9C
+ sample 136:
+ time = 3157784
+ flags = 1
+ data = length 210, hash BD45C31F
+ sample 137:
+ time = 3181003
+ flags = 1
+ data = length 207, hash 8774FF7B
+ sample 138:
+ time = 3204222
+ flags = 1
+ data = length 149, hash 4678C0E5
+ sample 139:
+ time = 3227441
+ flags = 1
+ data = length 161, hash E991035D
+ sample 140:
+ time = 3250660
+ flags = 1
+ data = length 197, hash C3013689
+ sample 141:
+ time = 3273879
+ flags = 1
+ data = length 208, hash E6C0237
+ sample 142:
+ time = 3297098
+ flags = 1
+ data = length 232, hash A330F188
+ sample 143:
+ time = 3320317
+ flags = 1
+ data = length 174, hash 2B69C34E
+track 1:
+ total output bytes = 141
+ sample count = 2
+ format 0:
+ id = 1
+ sampleMimeType = application/id3
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 55, hash A7EB51A0
+ sample 1:
+ time = 23219
+ flags = 1
+ data = length 86, hash 3FA72D40
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk b/tree/testdata/src/test/assets/ts/sample_with_junk
new file mode 100644
index 0000000..a83777f
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk.0.dump b/tree/testdata/src/test/assets/ts/sample_with_junk.0.dump
new file mode 100644
index 0000000..ae6d60a
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk.0.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=28281]]
+ getPosition(66733) = [[timeUs=66733, position=56751]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk.1.dump b/tree/testdata/src/test/assets/ts/sample_with_junk.1.dump
new file mode 100644
index 0000000..4a03d49
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk.1.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=28281]]
+ getPosition(66733) = [[timeUs=66733, position=56751]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 55610
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 88977
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 44699
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 70821
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 96944
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 123066
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk.2.dump b/tree/testdata/src/test/assets/ts/sample_with_junk.2.dump
new file mode 100644
index 0000000..7ed12db
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk.2.dump
@@ -0,0 +1,59 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=28281]]
+ getPosition(66733) = [[timeUs=66733, position=56751]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 77854
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 111221
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66943
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 93065
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 119188
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 145310
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk.3.dump b/tree/testdata/src/test/assets/ts/sample_with_junk.3.dump
new file mode 100644
index 0000000..603d151
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk.3.dump
@@ -0,0 +1,43 @@
+seekMap:
+ isSeekable = true
+ duration = 66733
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(33366) = [[timeUs=33366, position=28281]]
+ getPosition(66733) = [[timeUs=66733, position=56751]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+track 257:
+ total output bytes = 2508
+ sample count = 2
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 66733
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 1:
+ time = 92855
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_with_junk.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_with_junk.unknown_length.dump
new file mode 100644
index 0000000..e18961d
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_with_junk.unknown_length.dump
@@ -0,0 +1,56 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 3
+track 256:
+ total output bytes = 45026
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/mpeg2
+ width = 640
+ height = 426
+ initializationData:
+ data = length 22, hash CE183139
+ sample 0:
+ time = 33366
+ flags = 1
+ data = length 20711, hash 34341E8
+ sample 1:
+ time = 66733
+ flags = 0
+ data = length 18112, hash EC44B35B
+track 257:
+ total output bytes = 5015
+ sample count = 4
+ format 0:
+ id = 1/257
+ sampleMimeType = audio/mpeg-L2
+ maxInputSize = 4096
+ channelCount = 1
+ sampleRate = 44100
+ language = und
+ sample 0:
+ time = 22455
+ flags = 1
+ data = length 1253, hash 727FD1C6
+ sample 1:
+ time = 48577
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 2:
+ time = 74700
+ flags = 1
+ data = length 1254, hash 73FB07B8
+ sample 3:
+ time = 100822
+ flags = 1
+ data = length 1254, hash 73FB07B8
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/ts/sample_with_sdt.ts b/tree/testdata/src/test/assets/ts/sample_with_sdt.ts
similarity index 100%
rename from tree/library/extractor/src/test/assets/ts/sample_with_sdt.ts
rename to tree/testdata/src/test/assets/ts/sample_with_sdt.ts
Binary files differ
diff --git a/tree/library/core/src/test/assets/ttml/bitmap_percentage_region.xml b/tree/testdata/src/test/assets/ttml/bitmap_percentage_region.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/bitmap_percentage_region.xml
rename to tree/testdata/src/test/assets/ttml/bitmap_percentage_region.xml
diff --git a/tree/library/core/src/test/assets/ttml/bitmap_pixel_region.xml b/tree/testdata/src/test/assets/ttml/bitmap_pixel_region.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/bitmap_pixel_region.xml
rename to tree/testdata/src/test/assets/ttml/bitmap_pixel_region.xml
diff --git a/tree/library/core/src/test/assets/ttml/bitmap_unsupported_region.xml b/tree/testdata/src/test/assets/ttml/bitmap_unsupported_region.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/bitmap_unsupported_region.xml
rename to tree/testdata/src/test/assets/ttml/bitmap_unsupported_region.xml
diff --git a/tree/library/core/src/test/assets/ttml/chain_multiple_styles.xml b/tree/testdata/src/test/assets/ttml/chain_multiple_styles.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/chain_multiple_styles.xml
rename to tree/testdata/src/test/assets/ttml/chain_multiple_styles.xml
diff --git a/tree/library/core/src/test/assets/ttml/font_size.xml b/tree/testdata/src/test/assets/ttml/font_size.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/font_size.xml
rename to tree/testdata/src/test/assets/ttml/font_size.xml
diff --git a/tree/library/core/src/test/assets/ttml/font_size_empty.xml b/tree/testdata/src/test/assets/ttml/font_size_empty.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/font_size_empty.xml
rename to tree/testdata/src/test/assets/ttml/font_size_empty.xml
diff --git a/tree/library/core/src/test/assets/ttml/font_size_invalid.xml b/tree/testdata/src/test/assets/ttml/font_size_invalid.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/font_size_invalid.xml
rename to tree/testdata/src/test/assets/ttml/font_size_invalid.xml
diff --git a/tree/library/core/src/test/assets/ttml/font_size_no_unit.xml b/tree/testdata/src/test/assets/ttml/font_size_no_unit.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/font_size_no_unit.xml
rename to tree/testdata/src/test/assets/ttml/font_size_no_unit.xml
diff --git a/tree/library/core/src/test/assets/ttml/frame_rate.xml b/tree/testdata/src/test/assets/ttml/frame_rate.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/frame_rate.xml
rename to tree/testdata/src/test/assets/ttml/frame_rate.xml
diff --git a/tree/library/core/src/test/assets/ttml/inherit_and_override_style.xml b/tree/testdata/src/test/assets/ttml/inherit_and_override_style.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/inherit_and_override_style.xml
rename to tree/testdata/src/test/assets/ttml/inherit_and_override_style.xml
diff --git a/tree/library/core/src/test/assets/ttml/inherit_global_and_parent.xml b/tree/testdata/src/test/assets/ttml/inherit_global_and_parent.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/inherit_global_and_parent.xml
rename to tree/testdata/src/test/assets/ttml/inherit_global_and_parent.xml
diff --git a/tree/library/core/src/test/assets/ttml/inherit_multiple_styles.xml b/tree/testdata/src/test/assets/ttml/inherit_multiple_styles.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/inherit_multiple_styles.xml
rename to tree/testdata/src/test/assets/ttml/inherit_multiple_styles.xml
diff --git a/tree/library/core/src/test/assets/ttml/inherit_style.xml b/tree/testdata/src/test/assets/ttml/inherit_style.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/inherit_style.xml
rename to tree/testdata/src/test/assets/ttml/inherit_style.xml
diff --git a/tree/library/core/src/test/assets/ttml/inline_style_attributes.xml b/tree/testdata/src/test/assets/ttml/inline_style_attributes.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/inline_style_attributes.xml
rename to tree/testdata/src/test/assets/ttml/inline_style_attributes.xml
diff --git a/tree/library/core/src/test/assets/ttml/multiple_regions.xml b/tree/testdata/src/test/assets/ttml/multiple_regions.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/multiple_regions.xml
rename to tree/testdata/src/test/assets/ttml/multiple_regions.xml
diff --git a/tree/library/core/src/test/assets/ttml/no_underline_linethrough.xml b/tree/testdata/src/test/assets/ttml/no_underline_linethrough.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/no_underline_linethrough.xml
rename to tree/testdata/src/test/assets/ttml/no_underline_linethrough.xml
diff --git a/tree/testdata/src/test/assets/ttml/rubies.xml b/tree/testdata/src/test/assets/ttml/rubies.xml
new file mode 100644
index 0000000..0eb89da
--- /dev/null
+++ b/tree/testdata/src/test/assets/ttml/rubies.xml
@@ -0,0 +1,78 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
+ xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
+ xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
+ xmlns="http://www.w3.org/ns/ttml"
+ xmlns="http://www.w3.org/2006/10/ttaf1">
+ <body>
+ <div>
+ <!-- Base before and after text, one with explicit position -->
+ <p begin="10s" end="18s">
+ Cue with
+ <span tts:ruby="container" tts:rubyPosition="before">
+ <span tts:ruby="base">annotated</span>
+ <span tts:ruby="text">1st rubies</span>
+ </span>
+ <span tts:ruby="container">
+ <span tts:ruby="text">2nd rubies</span>
+ <span tts:ruby="base">text</span>.
+ </span>
+ </p>
+ </div>
+ <div>
+ <!-- Delimiter (parenthetical) text is stripped -->
+ <p begin="20s" end="28s">
+ Cue with
+ <span tts:ruby="container">
+ <span tts:ruby="text">rubies</span>
+ <span tts:ruby="base">annotated</span>
+ <span tts:ruby="delimiter">alt-text</span>
+ </span>
+ text.
+ </p>
+ </div>
+ <div>
+ <!-- No text section -> no span -->
+ <p begin="30s" end="38s">
+ Cue with
+ <span tts:ruby="container" tts:rubyPosition="before">
+ <span tts:ruby="base">annotated</span>
+ </span>
+ text.</p>
+ </div>
+ <div>
+ <!-- No base section -> text still stripped-->
+ <p begin="40s" end="48s">
+ Cue with
+ <span tts:ruby="container" tts:rubyPosition="before">
+ <span tts:ruby="text">rubies</span>
+ </span>
+ text.
+ </p>
+ </div>
+ <div>
+ <!-- No container section -> text still stripped-->
+ <p begin="50s" end="58s">
+ Cue with
+ <span tts:ruby="text">rubies</span>
+ <span tts:ruby="base">annotated</span>
+ text.
+ </p>
+ </div>
+ </body>
+</tt>
diff --git a/tree/testdata/src/test/assets/ttml/text_combine.xml b/tree/testdata/src/test/assets/ttml/text_combine.xml
new file mode 100644
index 0000000..4126058
--- /dev/null
+++ b/tree/testdata/src/test/assets/ttml/text_combine.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
+ xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
+ xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
+ xmlns="http://www.w3.org/ns/ttml"
+ xmlns="http://www.w3.org/2006/10/ttaf1">
+ <body>
+ <div>
+ <p begin="10s" end="18s">Text with <span tts:textCombine="all">combined</span> section.</p>
+ </div>
+ <div>
+ <p begin="20s" end="28s">Text with <span tts:textCombine="none">un-combined</span> section.</p>
+ </div>
+ <div>
+ <p begin="30s" end="38s">"No textCombine"</p>
+ </div>
+ </body>
+</tt>
diff --git a/tree/library/core/src/test/assets/ttml/vertical_text.xml b/tree/testdata/src/test/assets/ttml/vertical_text.xml
similarity index 100%
rename from tree/library/core/src/test/assets/ttml/vertical_text.xml
rename to tree/testdata/src/test/assets/ttml/vertical_text.xml
diff --git a/tree/library/core/src/test/assets/tx3g/initialization b/tree/testdata/src/test/assets/tx3g/initialization
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/initialization
rename to tree/testdata/src/test/assets/tx3g/initialization
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/initialization_all_defaults b/tree/testdata/src/test/assets/tx3g/initialization_all_defaults
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/initialization_all_defaults
rename to tree/testdata/src/test/assets/tx3g/initialization_all_defaults
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/no_subtitle b/tree/testdata/src/test/assets/tx3g/no_subtitle
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/no_subtitle
rename to tree/testdata/src/test/assets/tx3g/no_subtitle
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_just_text b/tree/testdata/src/test/assets/tx3g/sample_just_text
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_just_text
rename to tree/testdata/src/test/assets/tx3g/sample_just_text
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_utf16_be_no_styl b/tree/testdata/src/test/assets/tx3g/sample_utf16_be_no_styl
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_utf16_be_no_styl
rename to tree/testdata/src/test/assets/tx3g/sample_utf16_be_no_styl
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_utf16_le_no_styl b/tree/testdata/src/test/assets/tx3g/sample_utf16_le_no_styl
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_utf16_le_no_styl
rename to tree/testdata/src/test/assets/tx3g/sample_utf16_le_no_styl
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_with_multiple_styl b/tree/testdata/src/test/assets/tx3g/sample_with_multiple_styl
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_with_multiple_styl
rename to tree/testdata/src/test/assets/tx3g/sample_with_multiple_styl
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_with_other_extension b/tree/testdata/src/test/assets/tx3g/sample_with_other_extension
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_with_other_extension
rename to tree/testdata/src/test/assets/tx3g/sample_with_other_extension
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_with_styl b/tree/testdata/src/test/assets/tx3g/sample_with_styl
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_with_styl
rename to tree/testdata/src/test/assets/tx3g/sample_with_styl
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_with_styl_all_defaults b/tree/testdata/src/test/assets/tx3g/sample_with_styl_all_defaults
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_with_styl_all_defaults
rename to tree/testdata/src/test/assets/tx3g/sample_with_styl_all_defaults
Binary files differ
diff --git a/tree/library/core/src/test/assets/tx3g/sample_with_tbox b/tree/testdata/src/test/assets/tx3g/sample_with_tbox
similarity index 100%
rename from tree/library/core/src/test/assets/tx3g/sample_with_tbox
rename to tree/testdata/src/test/assets/tx3g/sample_with_tbox
Binary files differ
diff --git a/tree/extensions/vp9/src/androidTest/assets/bear-vp9-odd-dimensions.webm b/tree/testdata/src/test/assets/vp9/bear-vp9-odd-dimensions.webm
similarity index 100%
rename from tree/extensions/vp9/src/androidTest/assets/bear-vp9-odd-dimensions.webm
rename to tree/testdata/src/test/assets/vp9/bear-vp9-odd-dimensions.webm
Binary files differ
diff --git a/tree/extensions/vp9/src/androidTest/assets/bear-vp9.webm b/tree/testdata/src/test/assets/vp9/bear-vp9.webm
similarity index 100%
rename from tree/extensions/vp9/src/androidTest/assets/bear-vp9.webm
rename to tree/testdata/src/test/assets/vp9/bear-vp9.webm
Binary files differ
diff --git a/tree/extensions/vp9/src/androidTest/assets/invalid-bitstream.webm b/tree/testdata/src/test/assets/vp9/invalid-bitstream.webm
similarity index 100%
rename from tree/extensions/vp9/src/androidTest/assets/invalid-bitstream.webm
rename to tree/testdata/src/test/assets/vp9/invalid-bitstream.webm
Binary files differ
diff --git a/tree/extensions/vp9/src/androidTest/assets/roadtrip-vp92-10bit.webm b/tree/testdata/src/test/assets/vp9/roadtrip-vp92-10bit.webm
similarity index 100%
rename from tree/extensions/vp9/src/androidTest/assets/roadtrip-vp92-10bit.webm
rename to tree/testdata/src/test/assets/vp9/roadtrip-vp92-10bit.webm
Binary files differ
diff --git a/tree/library/extractor/src/test/assets/wav/sample.wav b/tree/testdata/src/test/assets/wav/sample.wav
similarity index 100%
rename from tree/library/extractor/src/test/assets/wav/sample.wav
rename to tree/testdata/src/test/assets/wav/sample.wav
Binary files differ
diff --git a/tree/testdata/src/test/assets/wav/sample.wav.0.dump b/tree/testdata/src/test/assets/wav/sample.wav.0.dump
new file mode 100644
index 0000000..8b6c4a5
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample.wav.0.dump
@@ -0,0 +1,60 @@
+seekMap:
+ isSeekable = true
+ duration = 1000000
+ getPosition(0) = [[timeUs=0, position=78]]
+ getPosition(1) = [[timeUs=0, position=78], [timeUs=22, position=80]]
+ getPosition(500000) = [[timeUs=500000, position=44178]]
+ getPosition(1000000) = [[timeUs=999977, position=88276]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 88200
+ sample count = 10
+ format 0:
+ averageBitrate = 705600
+ peakBitrate = 705600
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 8820, hash FAE27E28
+ sample 1:
+ time = 100000
+ flags = 1
+ data = length 8820, hash 21C3E9C3
+ sample 2:
+ time = 200000
+ flags = 1
+ data = length 8820, hash B51AD902
+ sample 3:
+ time = 300000
+ flags = 1
+ data = length 8820, hash 2F4B2CB4
+ sample 4:
+ time = 400000
+ flags = 1
+ data = length 8820, hash F0030CC2
+ sample 5:
+ time = 500000
+ flags = 1
+ data = length 8820, hash FF83DA46
+ sample 6:
+ time = 600000
+ flags = 1
+ data = length 8820, hash 685C1AB5
+ sample 7:
+ time = 700000
+ flags = 1
+ data = length 8820, hash BE63D51C
+ sample 8:
+ time = 800000
+ flags = 1
+ data = length 8820, hash 1E44EB8E
+ sample 9:
+ time = 900000
+ flags = 1
+ data = length 8820, hash 57C41232
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample.wav.1.dump b/tree/testdata/src/test/assets/wav/sample.wav.1.dump
new file mode 100644
index 0000000..65d3586
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample.wav.1.dump
@@ -0,0 +1,48 @@
+seekMap:
+ isSeekable = true
+ duration = 1000000
+ getPosition(0) = [[timeUs=0, position=78]]
+ getPosition(1) = [[timeUs=0, position=78], [timeUs=22, position=80]]
+ getPosition(500000) = [[timeUs=500000, position=44178]]
+ getPosition(1000000) = [[timeUs=999977, position=88276]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 58802
+ sample count = 7
+ format 0:
+ averageBitrate = 705600
+ peakBitrate = 705600
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 333333
+ flags = 1
+ data = length 8820, hash 31868A21
+ sample 1:
+ time = 433333
+ flags = 1
+ data = length 8820, hash AE3D77A2
+ sample 2:
+ time = 533333
+ flags = 1
+ data = length 8820, hash 966140CE
+ sample 3:
+ time = 633333
+ flags = 1
+ data = length 8820, hash CB405D7B
+ sample 4:
+ time = 733333
+ flags = 1
+ data = length 8820, hash 733BA3E6
+ sample 5:
+ time = 833333
+ flags = 1
+ data = length 8820, hash 7595D752
+ sample 6:
+ time = 933333
+ flags = 1
+ data = length 5882, hash C617B719
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample.wav.2.dump b/tree/testdata/src/test/assets/wav/sample.wav.2.dump
new file mode 100644
index 0000000..bd9c862
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample.wav.2.dump
@@ -0,0 +1,36 @@
+seekMap:
+ isSeekable = true
+ duration = 1000000
+ getPosition(0) = [[timeUs=0, position=78]]
+ getPosition(1) = [[timeUs=0, position=78], [timeUs=22, position=80]]
+ getPosition(500000) = [[timeUs=500000, position=44178]]
+ getPosition(1000000) = [[timeUs=999977, position=88276]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 29402
+ sample count = 4
+ format 0:
+ averageBitrate = 705600
+ peakBitrate = 705600
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 666666
+ flags = 1
+ data = length 8820, hash D6617E20
+ sample 1:
+ time = 766666
+ flags = 1
+ data = length 8820, hash 28C74B7A
+ sample 2:
+ time = 866666
+ flags = 1
+ data = length 8820, hash 680DEFC7
+ sample 3:
+ time = 966666
+ flags = 1
+ data = length 2942, hash 1D063CF0
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample.wav.3.dump b/tree/testdata/src/test/assets/wav/sample.wav.3.dump
new file mode 100644
index 0000000..ecf7512
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample.wav.3.dump
@@ -0,0 +1,24 @@
+seekMap:
+ isSeekable = true
+ duration = 1000000
+ getPosition(0) = [[timeUs=0, position=78]]
+ getPosition(1) = [[timeUs=0, position=78], [timeUs=22, position=80]]
+ getPosition(500000) = [[timeUs=500000, position=44178]]
+ getPosition(1000000) = [[timeUs=999977, position=88276]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 2
+ sample count = 1
+ format 0:
+ averageBitrate = 705600
+ peakBitrate = 705600
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 1000000
+ flags = 1
+ data = length 2, hash 116
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample.wav.unknown_length.dump b/tree/testdata/src/test/assets/wav/sample.wav.unknown_length.dump
new file mode 100644
index 0000000..8b6c4a5
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample.wav.unknown_length.dump
@@ -0,0 +1,60 @@
+seekMap:
+ isSeekable = true
+ duration = 1000000
+ getPosition(0) = [[timeUs=0, position=78]]
+ getPosition(1) = [[timeUs=0, position=78], [timeUs=22, position=80]]
+ getPosition(500000) = [[timeUs=500000, position=44178]]
+ getPosition(1000000) = [[timeUs=999977, position=88276]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 88200
+ sample count = 10
+ format 0:
+ averageBitrate = 705600
+ peakBitrate = 705600
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 8820, hash FAE27E28
+ sample 1:
+ time = 100000
+ flags = 1
+ data = length 8820, hash 21C3E9C3
+ sample 2:
+ time = 200000
+ flags = 1
+ data = length 8820, hash B51AD902
+ sample 3:
+ time = 300000
+ flags = 1
+ data = length 8820, hash 2F4B2CB4
+ sample 4:
+ time = 400000
+ flags = 1
+ data = length 8820, hash F0030CC2
+ sample 5:
+ time = 500000
+ flags = 1
+ data = length 8820, hash FF83DA46
+ sample 6:
+ time = 600000
+ flags = 1
+ data = length 8820, hash 685C1AB5
+ sample 7:
+ time = 700000
+ flags = 1
+ data = length 8820, hash BE63D51C
+ sample 8:
+ time = 800000
+ flags = 1
+ data = length 8820, hash 1E44EB8E
+ sample 9:
+ time = 900000
+ flags = 1
+ data = length 8820, hash 57C41232
+tracksEnded = true
diff --git a/tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav
similarity index 100%
rename from tree/library/extractor/src/test/assets/wav/sample_ima_adpcm.wav
rename to tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav
Binary files differ
diff --git a/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.0.dump b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.0.dump
new file mode 100644
index 0000000..992e8d0
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.0.dump
@@ -0,0 +1,64 @@
+seekMap:
+ isSeekable = true
+ duration = 1018185
+ getPosition(0) = [[timeUs=0, position=94]]
+ getPosition(1) = [[timeUs=0, position=94], [timeUs=46281, position=1118]]
+ getPosition(509092) = [[timeUs=462811, position=10334], [timeUs=509092, position=11358]]
+ getPosition(1018185) = [[timeUs=971904, position=21598]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 89804
+ sample count = 11
+ format 0:
+ averageBitrate = 177004
+ peakBitrate = 177004
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 8820, hash E90A457C
+ sample 1:
+ time = 100000
+ flags = 1
+ data = length 8820, hash EA798370
+ sample 2:
+ time = 200000
+ flags = 1
+ data = length 8820, hash A57ED989
+ sample 3:
+ time = 300000
+ flags = 1
+ data = length 8820, hash 8B681816
+ sample 4:
+ time = 400000
+ flags = 1
+ data = length 8820, hash 48177BEB
+ sample 5:
+ time = 500000
+ flags = 1
+ data = length 8820, hash 70197776
+ sample 6:
+ time = 600000
+ flags = 1
+ data = length 8820, hash DB4A4704
+ sample 7:
+ time = 700000
+ flags = 1
+ data = length 8820, hash 84A525D0
+ sample 8:
+ time = 800000
+ flags = 1
+ data = length 8820, hash 197A4377
+ sample 9:
+ time = 900000
+ flags = 1
+ data = length 8820, hash 6982BC91
+ sample 10:
+ time = 1000000
+ flags = 1
+ data = length 1604, hash 3DED68ED
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.1.dump b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.1.dump
new file mode 100644
index 0000000..b21964f
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.1.dump
@@ -0,0 +1,48 @@
+seekMap:
+ isSeekable = true
+ duration = 1018185
+ getPosition(0) = [[timeUs=0, position=94]]
+ getPosition(1) = [[timeUs=0, position=94], [timeUs=46281, position=1118]]
+ getPosition(509092) = [[timeUs=462811, position=10334], [timeUs=509092, position=11358]]
+ getPosition(1018185) = [[timeUs=971904, position=21598]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 61230
+ sample count = 7
+ format 0:
+ averageBitrate = 177004
+ peakBitrate = 177004
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 339395
+ flags = 1
+ data = length 8820, hash 25FCA092
+ sample 1:
+ time = 439395
+ flags = 1
+ data = length 8820, hash 9400B4BE
+ sample 2:
+ time = 539395
+ flags = 1
+ data = length 8820, hash 5BA7E45D
+ sample 3:
+ time = 639395
+ flags = 1
+ data = length 8820, hash 5AC42905
+ sample 4:
+ time = 739395
+ flags = 1
+ data = length 8820, hash D57059C
+ sample 5:
+ time = 839395
+ flags = 1
+ data = length 8820, hash DEF5C480
+ sample 6:
+ time = 939395
+ flags = 1
+ data = length 8310, hash 10B3FC93
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.2.dump b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.2.dump
new file mode 100644
index 0000000..d478a25
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.2.dump
@@ -0,0 +1,36 @@
+seekMap:
+ isSeekable = true
+ duration = 1018185
+ getPosition(0) = [[timeUs=0, position=94]]
+ getPosition(1) = [[timeUs=0, position=94], [timeUs=46281, position=1118]]
+ getPosition(509092) = [[timeUs=462811, position=10334], [timeUs=509092, position=11358]]
+ getPosition(1018185) = [[timeUs=971904, position=21598]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 32656
+ sample count = 4
+ format 0:
+ averageBitrate = 177004
+ peakBitrate = 177004
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 678790
+ flags = 1
+ data = length 8820, hash DB7FF64C
+ sample 1:
+ time = 778790
+ flags = 1
+ data = length 8820, hash B895DFDC
+ sample 2:
+ time = 878790
+ flags = 1
+ data = length 8820, hash E3AB416D
+ sample 3:
+ time = 978790
+ flags = 1
+ data = length 6196, hash E27E175A
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.3.dump b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.3.dump
new file mode 100644
index 0000000..6512731
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.3.dump
@@ -0,0 +1,24 @@
+seekMap:
+ isSeekable = true
+ duration = 1018185
+ getPosition(0) = [[timeUs=0, position=94]]
+ getPosition(1) = [[timeUs=0, position=94], [timeUs=46281, position=1118]]
+ getPosition(509092) = [[timeUs=462811, position=10334], [timeUs=509092, position=11358]]
+ getPosition(1018185) = [[timeUs=971904, position=21598]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 4082
+ sample count = 1
+ format 0:
+ averageBitrate = 177004
+ peakBitrate = 177004
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 1018185
+ flags = 1
+ data = length 4082, hash 4CB1A490
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.unknown_length.dump b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.unknown_length.dump
new file mode 100644
index 0000000..992e8d0
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_ima_adpcm.wav.unknown_length.dump
@@ -0,0 +1,64 @@
+seekMap:
+ isSeekable = true
+ duration = 1018185
+ getPosition(0) = [[timeUs=0, position=94]]
+ getPosition(1) = [[timeUs=0, position=94], [timeUs=46281, position=1118]]
+ getPosition(509092) = [[timeUs=462811, position=10334], [timeUs=509092, position=11358]]
+ getPosition(1018185) = [[timeUs=971904, position=21598]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 89804
+ sample count = 11
+ format 0:
+ averageBitrate = 177004
+ peakBitrate = 177004
+ sampleMimeType = audio/raw
+ maxInputSize = 8820
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = 2
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 8820, hash E90A457C
+ sample 1:
+ time = 100000
+ flags = 1
+ data = length 8820, hash EA798370
+ sample 2:
+ time = 200000
+ flags = 1
+ data = length 8820, hash A57ED989
+ sample 3:
+ time = 300000
+ flags = 1
+ data = length 8820, hash 8B681816
+ sample 4:
+ time = 400000
+ flags = 1
+ data = length 8820, hash 48177BEB
+ sample 5:
+ time = 500000
+ flags = 1
+ data = length 8820, hash 70197776
+ sample 6:
+ time = 600000
+ flags = 1
+ data = length 8820, hash DB4A4704
+ sample 7:
+ time = 700000
+ flags = 1
+ data = length 8820, hash 84A525D0
+ sample 8:
+ time = 800000
+ flags = 1
+ data = length 8820, hash 197A4377
+ sample 9:
+ time = 900000
+ flags = 1
+ data = length 8820, hash 6982BC91
+ sample 10:
+ time = 1000000
+ flags = 1
+ data = length 1604, hash 3DED68ED
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/wav/sample_with_trailing_bytes.wav b/tree/testdata/src/test/assets/wav/sample_with_trailing_bytes.wav
new file mode 100644
index 0000000..0b06efc
--- /dev/null
+++ b/tree/testdata/src/test/assets/wav/sample_with_trailing_bytes.wav
Binary files differ
diff --git a/tree/library/core/src/test/assets/webvtt/empty b/tree/testdata/src/test/assets/webvtt/empty
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/empty
rename to tree/testdata/src/test/assets/webvtt/empty
diff --git a/tree/library/core/src/test/assets/webvtt/typical b/tree/testdata/src/test/assets/webvtt/typical
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/typical
rename to tree/testdata/src/test/assets/webvtt/typical
diff --git a/tree/library/core/src/test/assets/webvtt/typical_with_bad_timestamps b/tree/testdata/src/test/assets/webvtt/typical_with_bad_timestamps
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/typical_with_bad_timestamps
rename to tree/testdata/src/test/assets/webvtt/typical_with_bad_timestamps
diff --git a/tree/library/core/src/test/assets/webvtt/typical_with_comments b/tree/testdata/src/test/assets/webvtt/typical_with_comments
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/typical_with_comments
rename to tree/testdata/src/test/assets/webvtt/typical_with_comments
diff --git a/tree/library/core/src/test/assets/webvtt/typical_with_identifiers b/tree/testdata/src/test/assets/webvtt/typical_with_identifiers
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/typical_with_identifiers
rename to tree/testdata/src/test/assets/webvtt/typical_with_identifiers
diff --git a/tree/library/core/src/test/assets/webvtt/with_bad_cue_header b/tree/testdata/src/test/assets/webvtt/with_bad_cue_header
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_bad_cue_header
rename to tree/testdata/src/test/assets/webvtt/with_bad_cue_header
diff --git a/tree/library/core/src/test/assets/webvtt/with_bom b/tree/testdata/src/test/assets/webvtt/with_bom
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_bom
rename to tree/testdata/src/test/assets/webvtt/with_bom
diff --git a/tree/library/core/src/test/assets/webvtt/with_css_complex_selectors b/tree/testdata/src/test/assets/webvtt/with_css_complex_selectors
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_css_complex_selectors
rename to tree/testdata/src/test/assets/webvtt/with_css_complex_selectors
diff --git a/tree/library/core/src/test/assets/webvtt/with_css_styles b/tree/testdata/src/test/assets/webvtt/with_css_styles
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_css_styles
rename to tree/testdata/src/test/assets/webvtt/with_css_styles
diff --git a/tree/library/core/src/test/assets/webvtt/with_css_text_combine_upright b/tree/testdata/src/test/assets/webvtt/with_css_text_combine_upright
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_css_text_combine_upright
rename to tree/testdata/src/test/assets/webvtt/with_css_text_combine_upright
diff --git a/tree/library/core/src/test/assets/webvtt/with_positioning b/tree/testdata/src/test/assets/webvtt/with_positioning
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_positioning
rename to tree/testdata/src/test/assets/webvtt/with_positioning
diff --git a/tree/library/core/src/test/assets/webvtt/with_tags b/tree/testdata/src/test/assets/webvtt/with_tags
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_tags
rename to tree/testdata/src/test/assets/webvtt/with_tags
diff --git a/tree/library/core/src/test/assets/webvtt/with_vertical b/tree/testdata/src/test/assets/webvtt/with_vertical
similarity index 100%
rename from tree/library/core/src/test/assets/webvtt/with_vertical
rename to tree/testdata/src/test/assets/webvtt/with_vertical
diff --git a/tree/testutils/build.gradle b/tree/testutils/build.gradle
index 9309c23..e4cfd7c 100644
--- a/tree/testutils/build.gradle
+++ b/tree/testutils/build.gradle
@@ -38,9 +38,9 @@
api 'com.google.truth:truth:' + truthVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
+ compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'library-core')
- testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
index 0814849..8351001 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
@@ -22,7 +22,6 @@
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.IllegalSeekPositionException;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.PlayerMessage.Target;
@@ -36,6 +35,7 @@
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.Log;
@@ -64,16 +64,18 @@
*
* @param player The player to which the action should be applied.
* @param trackSelector The track selector to which the action should be applied.
- * @param surface The surface to use when applying actions.
+ * @param surface The surface to use when applying actions, or {@code null} if no surface is
+ * needed.
* @param handler The handler to use to pass to the next action.
- * @param nextAction The next action to schedule immediately after this action finished.
+ * @param nextAction The next action to schedule immediately after this action finished, or {@code
+ * null} if there's no next action.
*/
public final void doActionAndScheduleNext(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper handler,
- ActionNode nextAction) {
+ @Nullable ActionNode nextAction) {
if (description != null) {
Log.i(tag, description);
}
@@ -86,16 +88,18 @@
*
* @param player The player to which the action should be applied.
* @param trackSelector The track selector to which the action should be applied.
- * @param surface The surface to use when applying actions.
+ * @param surface The surface to use when applying actions, or {@code null} if no surface is
+ * needed.
* @param handler The handler to use to pass to the next action.
- * @param nextAction The next action to schedule immediately after this action finished.
+ * @param nextAction The next action to schedule immediately after this action finished, or {@code
+ * null} if there's no next action.
*/
protected void doActionAndScheduleNextImpl(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper handler,
- ActionNode nextAction) {
+ @Nullable ActionNode nextAction) {
doActionImpl(player, trackSelector, surface);
if (nextAction != null) {
nextAction.schedule(player, trackSelector, surface, handler);
@@ -108,15 +112,16 @@
*
* @param player The player to which the action should be applied.
* @param trackSelector The track selector to which the action should be applied.
- * @param surface The surface to use when applying actions.
+ * @param surface The surface to use when applying actions, or {@code null} if no surface is
+ * needed.
*/
protected abstract void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface);
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface);
/** Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}. */
public static final class Seek extends Action {
- private final Integer windowIndex;
+ @Nullable private final Integer windowIndex;
private final long positionMs;
private final boolean catchIllegalSeekException;
@@ -151,7 +156,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
try {
if (windowIndex == null) {
player.seekTo(positionMs);
@@ -189,7 +194,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setMediaSources(Arrays.asList(mediaSources), windowIndex, positionMs);
}
}
@@ -210,7 +215,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.addMediaSources(Arrays.asList(mediaSources));
}
}
@@ -235,7 +240,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setMediaSources(Arrays.asList(mediaSources), resetPosition);
}
}
@@ -259,7 +264,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.moveMediaItem(currentIndex, newIndex);
}
}
@@ -280,7 +285,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.removeMediaItem(index);
}
}
@@ -304,7 +309,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.removeMediaItems(fromIndex, toIndex);
}
}
@@ -319,7 +324,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.clearMediaItems();
}
}
@@ -329,7 +334,7 @@
private static final String STOP_ACTION_TAG = "Stop";
- private final Boolean reset;
+ @Nullable private final Boolean reset;
/**
* Action will call {@link Player#stop()}.
@@ -354,7 +359,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
if (reset == null) {
player.stop();
} else {
@@ -379,7 +384,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setPlayWhenReady(playWhenReady);
}
}
@@ -406,7 +411,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
trackSelector.setParameters(
trackSelector.buildUponParameters().setRendererDisabled(rendererIndex, disabled));
}
@@ -422,7 +427,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.clearVideoSurface();
}
}
@@ -437,8 +442,8 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
- player.setVideoSurface(surface);
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
+ player.setVideoSurface(Assertions.checkNotNull(surface));
}
}
@@ -462,7 +467,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setAudioAttributes(audioAttributes, handleAudioFocus);
}
}
@@ -476,7 +481,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.prepare();
}
}
@@ -491,13 +496,13 @@
* @param repeatMode The repeat mode.
*/
public SetRepeatMode(String tag, @Player.RepeatMode int repeatMode) {
- super(tag, "SetRepeatMode: " + repeatMode);
+ super(tag, "SetRepeatMode:" + repeatMode);
this.repeatMode = repeatMode;
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setRepeatMode(repeatMode);
}
}
@@ -518,7 +523,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setShuffleOrder(shuffleOrder);
}
}
@@ -533,13 +538,13 @@
* @param shuffleModeEnabled Whether shuffling is enabled.
*/
public SetShuffleModeEnabled(String tag, boolean shuffleModeEnabled) {
- super(tag, "SetShuffleModeEnabled: " + shuffleModeEnabled);
+ super(tag, "SetShuffleModeEnabled:" + shuffleModeEnabled);
this.shuffleModeEnabled = shuffleModeEnabled;
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player.setShuffleModeEnabled(shuffleModeEnabled);
}
}
@@ -585,7 +590,9 @@
@Override
protected void doActionImpl(
- final SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ final SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface) {
if (target instanceof PlayerTarget) {
((PlayerTarget) target).setPlayer(player);
}
@@ -601,24 +608,26 @@
}
}
- /** Calls {@link Player#setPlaybackParameters(PlaybackParameters)}. */
- public static final class SetPlaybackParameters extends Action {
+ /** Calls {@link Player#setPlaybackSpeed(float)}. */
+ public static final class SetPlaybackSpeed extends Action {
- private final PlaybackParameters playbackParameters;
+ private final float playbackSpeed;
/**
+ * Creates a set playback speed action instance.
+ *
* @param tag A tag to use for logging.
- * @param playbackParameters The playback parameters.
+ * @param playbackSpeed The playback speed.
*/
- public SetPlaybackParameters(String tag, PlaybackParameters playbackParameters) {
- super(tag, "SetPlaybackParameters:" + playbackParameters);
- this.playbackParameters = playbackParameters;
+ public SetPlaybackSpeed(String tag, float playbackSpeed) {
+ super(tag, "SetPlaybackSpeed:" + playbackSpeed);
+ this.playbackSpeed = playbackSpeed;
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
- player.setPlaybackParameters(playbackParameters);
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
+ player.setPlaybackSpeed(playbackSpeed);
}
}
@@ -638,7 +647,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
player
.createMessage(
(messageType, payload) -> {
@@ -663,18 +672,18 @@
* @param positionMs The position in that window at which the player should be paused again.
*/
public PlayUntilPosition(String tag, int windowIndex, long positionMs) {
- super(tag, "PlayUntilPosition:" + windowIndex + "," + positionMs);
+ super(tag, "PlayUntilPosition:" + windowIndex + ":" + positionMs);
this.windowIndex = windowIndex;
this.positionMs = positionMs;
}
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
Handler testThreadHandler = Util.createHandler();
// Schedule a message on the playback thread to ensure the player is paused immediately.
player
@@ -695,20 +704,22 @@
})
.setPosition(windowIndex, positionMs)
.send();
- // Schedule another message on this test thread to continue action schedule.
- player
- .createMessage(
- (messageType, payload) ->
- nextAction.schedule(player, trackSelector, surface, handler))
- .setPosition(windowIndex, positionMs)
- .setHandler(testThreadHandler)
- .send();
+ if (nextAction != null) {
+ // Schedule another message on this test thread to continue action schedule.
+ player
+ .createMessage(
+ (messageType, payload) ->
+ nextAction.schedule(player, trackSelector, surface, handler))
+ .setPosition(windowIndex, positionMs)
+ .setHandler(testThreadHandler)
+ .send();
+ }
player.play();
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
@@ -724,11 +735,14 @@
* Creates action waiting for a timeline change for a given reason.
*
* @param tag A tag to use for logging.
- * @param expectedTimeline The expected timeline or null if any timeline change is relevant.
+ * @param expectedTimeline The expected timeline or {@code null} if any timeline change is
+ * relevant.
* @param expectedReason The expected timeline change reason.
*/
public WaitForTimelineChanged(
- String tag, Timeline expectedTimeline, @Player.TimelineChangeReason int expectedReason) {
+ String tag,
+ @Nullable Timeline expectedTimeline,
+ @Player.TimelineChangeReason int expectedReason) {
super(tag, "WaitForTimelineChanged");
this.expectedTimeline = expectedTimeline != null ? new NoUidTimeline(expectedTimeline) : null;
this.ignoreExpectedReason = false;
@@ -749,11 +763,11 @@
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
@@ -779,7 +793,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
@@ -794,11 +808,11 @@
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
@@ -814,14 +828,14 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
/**
* Waits for a specified playWhenReady value, returning either immediately or after a call to
- * {@link Player.EventListener#onPlayerStateChanged(boolean, int)}.
+ * {@link Player.EventListener#onPlayWhenReadyChanged(boolean, int)}.
*/
public static final class WaitForPlayWhenReady extends Action {
@@ -840,9 +854,9 @@
protected void doActionAndScheduleNextImpl(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper handler,
- ActionNode nextAction) {
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
@@ -852,8 +866,8 @@
player.addListener(
new Player.EventListener() {
@Override
- public void onPlayerStateChanged(
- boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlayWhenReadyChanged(
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
if (targetPlayWhenReady == playWhenReady) {
player.removeListener(this);
nextAction.schedule(player, trackSelector, surface, handler);
@@ -865,14 +879,14 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
/**
* Waits for a specified playback state, returning either immediately or after a call to {@link
- * Player.EventListener#onPlayerStateChanged(boolean, int)}.
+ * Player.EventListener#onPlaybackStateChanged(int)}.
*/
public static final class WaitForPlaybackState extends Action {
@@ -889,11 +903,11 @@
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
@@ -903,8 +917,7 @@
player.addListener(
new Player.EventListener() {
@Override
- public void onPlayerStateChanged(
- boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
if (targetPlaybackState == playbackState) {
player.removeListener(this);
nextAction.schedule(player, trackSelector, surface, handler);
@@ -916,7 +929,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
@@ -940,34 +953,30 @@
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
PlayerTarget.Callback callback =
- new PlayerTarget.Callback() {
- @Override
- public void onMessageArrived() {
- nextAction.schedule(player, trackSelector, surface, handler);
- }
- };
+ () -> nextAction.schedule(player, trackSelector, surface, handler);
+
playerTarget.setCallback(callback);
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
/**
* Waits for a specified loading state, returning either immediately or after a call to {@link
- * Player.EventListener#onLoadingChanged(boolean)}.
+ * Player.EventListener#onIsLoadingChanged(boolean)}.
*/
public static final class WaitForIsLoading extends Action {
@@ -984,11 +993,11 @@
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
@@ -998,7 +1007,7 @@
player.addListener(
new Player.EventListener() {
@Override
- public void onLoadingChanged(boolean isLoading) {
+ public void onIsLoadingChanged(boolean isLoading) {
if (targetIsLoading == isLoading) {
player.removeListener(this);
nextAction.schedule(player, trackSelector, surface, handler);
@@ -1010,42 +1019,42 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
- /** Waits for {@link Player.EventListener#onSeekProcessed()}. */
- public static final class WaitForSeekProcessed extends Action {
+ /** Waits until the player acknowledged all pending player commands. */
+ public static final class WaitForPendingPlayerCommands extends Action {
/** @param tag A tag to use for logging. */
- public WaitForSeekProcessed(String tag) {
- super(tag, "WaitForSeekProcessed");
+ public WaitForPendingPlayerCommands(String tag) {
+ super(tag, "WaitForPendingPlayerCommands");
}
@Override
protected void doActionAndScheduleNextImpl(
- final SimpleExoPlayer player,
- final DefaultTrackSelector trackSelector,
- final Surface surface,
- final HandlerWrapper handler,
- final ActionNode nextAction) {
+ SimpleExoPlayer player,
+ DefaultTrackSelector trackSelector,
+ @Nullable Surface surface,
+ HandlerWrapper handler,
+ @Nullable ActionNode nextAction) {
if (nextAction == null) {
return;
}
- player.addListener(
- new Player.EventListener() {
- @Override
- public void onSeekProcessed() {
- player.removeListener(this);
- nextAction.schedule(player, trackSelector, surface, handler);
- }
- });
+ // Send message to player that will arrive after all other pending commands. Thus, the message
+ // execution on the app thread will also happen after all other pending command
+ // acknowledgements have arrived back on the app thread.
+ player
+ .createMessage(
+ (type, data) -> nextAction.schedule(player, trackSelector, surface, handler))
+ .setHandler(Util.createHandler())
+ .send();
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
@@ -1063,7 +1072,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
if (runnable instanceof PlayerRunnable) {
((PlayerRunnable) runnable).setPlayer(player);
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
index 9a9cfd5..d116fdc 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
@@ -20,7 +20,6 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.PlayerMessage.Target;
@@ -36,7 +35,7 @@
import com.google.android.exoplayer2.testutil.Action.SendMessages;
import com.google.android.exoplayer2.testutil.Action.SetAudioAttributes;
import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady;
-import com.google.android.exoplayer2.testutil.Action.SetPlaybackParameters;
+import com.google.android.exoplayer2.testutil.Action.SetPlaybackSpeed;
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
@@ -46,14 +45,15 @@
import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException;
import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading;
import com.google.android.exoplayer2.testutil.Action.WaitForMessage;
+import com.google.android.exoplayer2.testutil.Action.WaitForPendingPlayerCommands;
import com.google.android.exoplayer2.testutil.Action.WaitForPlayWhenReady;
import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState;
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
-import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed;
import com.google.android.exoplayer2.testutil.Action.WaitForTimelineChanged;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.HandlerWrapper;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Schedules a sequence of {@link Action}s for execution during a test.
@@ -89,7 +89,8 @@
*
* @param player The player to which actions should be applied.
* @param trackSelector The track selector to which actions should be applied.
- * @param surface The surface to use when applying actions.
+ * @param surface The surface to use when applying actions, or {@code null} if no surface is
+ * needed.
* @param mainHandler A handler associated with the main thread of the host activity.
* @param callback A {@link Callback} to notify when the action schedule finishes, or null if no
* notification is needed.
@@ -97,7 +98,7 @@
/* package */ void start(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper mainHandler,
@Nullable Callback callback) {
callbackAction.setCallback(callback);
@@ -197,28 +198,30 @@
*/
public Builder seekAndWait(long positionMs) {
return apply(new Seek(tag, positionMs))
- .apply(new WaitForSeekProcessed(tag))
.apply(new WaitForPlaybackState(tag, Player.STATE_READY));
}
/**
- * Schedules a delay until the player indicates that a seek has been processed.
+ * Schedules a delay until all pending player commands have been handled.
+ *
+ * <p>A command is considered as having been handled if it arrived on the playback thread and
+ * the player acknowledged that it received the command back to the app thread.
*
* @return The builder, for convenience.
*/
- public Builder waitForSeekProcessed() {
- return apply(new WaitForSeekProcessed(tag));
+ public Builder waitForPendingPlayerCommands() {
+ return apply(new WaitForPendingPlayerCommands(tag));
}
/**
- * Schedules a playback parameters setting action.
+ * Schedules a playback speed setting action.
*
- * @param playbackParameters The playback parameters to set.
+ * @param playbackSpeed The playback speed to set.
* @return The builder, for convenience.
- * @see Player#setPlaybackParameters(PlaybackParameters)
+ * @see Player#setPlaybackSpeed(float)
*/
- public Builder setPlaybackParameters(PlaybackParameters playbackParameters) {
- return apply(new SetPlaybackParameters(tag, playbackParameters));
+ public Builder setPlaybackSpeed(float playbackSpeed) {
+ return apply(new SetPlaybackSpeed(tag, playbackSpeed));
}
/**
@@ -607,9 +610,9 @@
void onMessageArrived();
}
- private SimpleExoPlayer player;
+ @Nullable private SimpleExoPlayer player;
private boolean hasArrived;
- private Callback callback;
+ @Nullable private Callback callback;
public void setCallback(Callback callback) {
this.callback = callback;
@@ -629,7 +632,7 @@
@Override
public final void handleMessage(int messageType, @Nullable Object message) {
- handleMessage(player, messageType, message);
+ handleMessage(Assertions.checkStateNotNull(player), messageType, message);
if (callback != null) {
hasArrived = true;
callback.onMessageArrived();
@@ -643,7 +646,7 @@
*/
public abstract static class PlayerRunnable implements Runnable {
- private SimpleExoPlayer player;
+ @Nullable private SimpleExoPlayer player;
/** Executes Runnable with reference to player. */
public abstract void run(SimpleExoPlayer player);
@@ -655,7 +658,7 @@
@Override
public final void run() {
- run(player);
+ run(Assertions.checkStateNotNull(player));
}
}
@@ -666,12 +669,12 @@
private final long delayMs;
private final long repeatIntervalMs;
- private ActionNode next;
+ @Nullable private ActionNode next;
- private SimpleExoPlayer player;
- private DefaultTrackSelector trackSelector;
- private Surface surface;
- private HandlerWrapper mainHandler;
+ private @MonotonicNonNull SimpleExoPlayer player;
+ private @MonotonicNonNull DefaultTrackSelector trackSelector;
+ @Nullable private Surface surface;
+ private @MonotonicNonNull HandlerWrapper mainHandler;
/**
* @param action The wrapped action.
@@ -708,13 +711,13 @@
*
* @param player The player to which actions should be applied.
* @param trackSelector The track selector to which actions should be applied.
- * @param surface The surface to use when applying actions.
+ * @param surface The surface to use when applying actions, or {@code null}.
* @param mainHandler A handler associated with the main thread of the host activity.
*/
public void schedule(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper mainHandler) {
this.player = player;
this.trackSelector = trackSelector;
@@ -729,14 +732,20 @@
@Override
public void run() {
- action.doActionAndScheduleNext(player, trackSelector, surface, mainHandler, next);
+ action.doActionAndScheduleNext(
+ Assertions.checkStateNotNull(player),
+ Assertions.checkStateNotNull(trackSelector),
+ surface,
+ Assertions.checkStateNotNull(mainHandler),
+ next);
if (repeatIntervalMs != C.TIME_UNSET) {
mainHandler.postDelayed(
new Runnable() {
@Override
public void run() {
- action.doActionAndScheduleNext(player, trackSelector, surface, mainHandler, null);
- mainHandler.postDelayed(this, repeatIntervalMs);
+ action.doActionAndScheduleNext(
+ player, trackSelector, surface, mainHandler, /* nextAction= */ null);
+ mainHandler.postDelayed(/* runnable= */ this, repeatIntervalMs);
}
},
repeatIntervalMs);
@@ -756,7 +765,7 @@
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Do nothing.
}
}
@@ -780,18 +789,19 @@
protected void doActionAndScheduleNextImpl(
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
- Surface surface,
+ @Nullable Surface surface,
HandlerWrapper handler,
- ActionNode nextAction) {
+ @Nullable ActionNode nextAction) {
Assertions.checkArgument(nextAction == null);
+ @Nullable Callback callback = this.callback;
if (callback != null) {
- handler.post(() -> callback.onActionScheduleFinished());
+ handler.post(callback::onActionScheduleFinished);
}
}
@Override
protected void doActionImpl(
- SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
+ SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
// Not triggered.
}
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.java
index 1d25429..a7d5e13 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/AutoAdvancingFakeClock.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.util.HandlerWrapper;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* {@link FakeClock} extension which automatically advances time whenever an empty message is
@@ -25,7 +26,7 @@
*/
public final class AutoAdvancingFakeClock extends FakeClock {
- private HandlerWrapper autoAdvancingHandler;
+ private @MonotonicNonNull HandlerWrapper autoAdvancingHandler;
public AutoAdvancingFakeClock() {
super(/* initialTimeMs= */ 0);
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java
index 4ea4c08..118f571 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java
@@ -26,6 +26,7 @@
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
@@ -44,7 +45,7 @@
ArrayList<FakeData> allData = fakeDataSet.getAllData();
dataSpecs = new DataSpec[allData.size()];
for (int i = 0; i < dataSpecs.length; i++) {
- dataSpecs[i] = new DataSpec(allData.get(i).uri);
+ dataSpecs[i] = new DataSpec(Assertions.checkNotNull(allData.get(i).uri));
}
}
@@ -74,7 +75,7 @@
}
public byte[] getData(int i) {
- return fakeDataSet.getData(dataSpecs[i].uri).getData();
+ return Assertions.checkNotNull(fakeDataSet.getData(dataSpecs[i].uri)).getData();
}
public DataSpec getDataSpec(int i) {
@@ -82,10 +83,10 @@
}
public RequestSet useBoundedDataSpecFor(String uriString) {
- FakeData data = fakeDataSet.getData(uriString);
+ FakeData data = Assertions.checkStateNotNull(fakeDataSet.getData(uriString));
for (int i = 0; i < dataSpecs.length; i++) {
DataSpec spec = dataSpecs[i];
- if (spec.uri.getPath().equals(uriString)) {
+ if (Assertions.checkNotNull(spec.uri.getPath()).equals(uriString)) {
dataSpecs[i] = spec.subrange(0, data.getData().length);
return this;
}
@@ -148,13 +149,10 @@
*/
public static void assertReadData(DataSource dataSource, DataSpec dataSpec, byte[] expected)
throws IOException {
- byte[] bytes = null;
try (DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec)) {
- bytes = Util.toByteArray(inputStream);
- } catch (IOException e) {
- // Ignore
+ byte[] bytes = Util.toByteArray(inputStream);
+ assertThat(bytes).isEqualTo(expected);
}
- assertThat(bytes).isEqualTo(expected);
}
/** Asserts that the cache is empty. */
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java
index 7a766e2..963d940 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java
@@ -21,6 +21,7 @@
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.audio.ForwardingAudioSink;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException;
@@ -80,7 +81,8 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
+ public boolean handleBuffer(
+ ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException {
// handleBuffer is called repeatedly with the same buffer until it's been fully consumed by the
// sink. We only want to dump each buffer once, and we need to do so before the sink being
@@ -89,7 +91,7 @@
interceptedData.add(new DumpableBuffer(buffer, presentationTimeUs));
currentBuffer = buffer;
}
- boolean fullyConsumed = super.handleBuffer(buffer, presentationTimeUs);
+ boolean fullyConsumed = super.handleBuffer(buffer, presentationTimeUs, encodedAccessUnitCount);
if (fullyConsumed) {
currentBuffer = null;
}
@@ -122,7 +124,7 @@
if (WRITE_DUMP) {
File directory = context.getExternalFilesDir(null);
File file = new File(directory, dumpFile);
- file.getParentFile().mkdirs();
+ Assertions.checkStateNotNull(file.getParentFile()).mkdirs();
PrintWriter out = new PrintWriter(file);
out.print(actual);
out.close();
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecoderCountersUtil.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecoderCountersUtil.java
index 3074437..fb00138 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecoderCountersUtil.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecoderCountersUtil.java
@@ -98,4 +98,20 @@
.isAtMost(limit);
}
+ public static void assertVideoFrameProcessingOffsetSampleCount(
+ String name, DecoderCounters counters, int minCount, int maxCount) {
+ int actual = counters.videoFrameProcessingOffsetCount;
+ assertWithMessage(
+ "Codec("
+ + name
+ + ") videoFrameProcessingOffsetSampleCount "
+ + actual
+ + ". Expected in range ["
+ + minCount
+ + ", "
+ + maxCount
+ + "].")
+ .that(minCount <= actual && actual <= maxCount)
+ .isTrue();
+ }
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DefaultRenderersFactoryAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DefaultRenderersFactoryAsserts.java
index 3720b8a..d865ea0 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DefaultRenderersFactoryAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/DefaultRenderersFactoryAsserts.java
@@ -93,7 +93,6 @@
new VideoRendererEventListener() {},
new AudioRendererEventListener() {},
(List<Cue> cues) -> {},
- (Metadata metadata) -> {},
- /* drmSessionManager= */ null);
+ (Metadata metadata) -> {});
}
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Dumper.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Dumper.java
index e346122..8175c90 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Dumper.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/Dumper.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.testutil;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.util.Arrays;
import java.util.Locale;
@@ -44,7 +45,7 @@
sb = new StringBuilder();
}
- public Dumper add(String field, Object value) {
+ public Dumper add(String field, @Nullable Object value) {
return addString(field + " = " + value + '\n');
}
@@ -53,9 +54,14 @@
return this;
}
- public Dumper add(String field, byte[] value) {
- String string = String.format(Locale.US, "%s = length %d, hash %X\n", field, value.length,
- Arrays.hashCode(value));
+ public Dumper add(String field, @Nullable byte[] value) {
+ String string =
+ String.format(
+ Locale.US,
+ "%s = length %d, hash %X\n",
+ field,
+ value == null ? 0 : value.length,
+ Arrays.hashCode(value));
return addString(string);
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
index cb5ef0f..ea3c74f 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
@@ -18,9 +18,10 @@
import static com.google.common.truth.Truth.assertWithMessage;
import android.os.ConditionVariable;
-import android.os.Looper;
import android.os.SystemClock;
import android.view.Surface;
+import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -31,7 +32,6 @@
import com.google.android.exoplayer2.audio.DefaultAudioSink;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.HostActivity.HostedTest;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@@ -41,6 +41,8 @@
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link HostedTest} for {@link ExoPlayer} playback tests. */
public abstract class ExoHostedTest implements AnalyticsListener, HostedTest {
@@ -64,12 +66,12 @@
private final DecoderCounters audioDecoderCounters;
private final ConditionVariable testFinished;
- private ActionSchedule pendingSchedule;
- private HandlerWrapper actionHandler;
- private DefaultTrackSelector trackSelector;
- private SimpleExoPlayer player;
- private Surface surface;
- private ExoPlaybackException playerError;
+ @Nullable private ActionSchedule pendingSchedule;
+ private @MonotonicNonNull SimpleExoPlayer player;
+ private @MonotonicNonNull HandlerWrapper actionHandler;
+ private @MonotonicNonNull DefaultTrackSelector trackSelector;
+ private @MonotonicNonNull Surface surface;
+ private @MonotonicNonNull ExoPlaybackException playerError;
private boolean playerWasPrepared;
private boolean playing;
@@ -115,7 +117,7 @@
* @param schedule The schedule.
*/
public final void setSchedule(ActionSchedule schedule) {
- if (player == null) {
+ if (!isStarted()) {
pendingSchedule = schedule;
} else {
schedule.start(player, trackSelector, surface, actionHandler, /* callback= */ null);
@@ -125,7 +127,7 @@
// HostedTest implementation
@Override
- public final void onStart(HostActivity host, Surface surface) {
+ public final void onStart(HostActivity host, Surface surface, FrameLayout overlayFrameLayout) {
this.surface = surface;
// Build the player.
trackSelector = buildTrackSelector(host);
@@ -135,13 +137,15 @@
player.addAnalyticsListener(this);
player.addAnalyticsListener(new EventLogger(trackSelector, tag));
// Schedule any pending actions.
- actionHandler = Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null);
+ actionHandler = Clock.DEFAULT.createHandler(Util.getLooper(), /* callback= */ null);
if (pendingSchedule != null) {
pendingSchedule.start(player, trackSelector, surface, actionHandler, /* callback= */ null);
pendingSchedule = null;
}
- DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = buildDrmSessionManager(userAgent);
- player.setMediaSource(buildSource(host, Util.getUserAgent(host, userAgent), drmSessionManager));
+ DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent);
+ player.setMediaSource(
+ buildSource(
+ host, Util.getUserAgent(host, userAgent), drmSessionManager, overlayFrameLayout));
player.prepare();
}
@@ -157,10 +161,10 @@
@Override
public final void onFinished() {
- onTestFinished(audioDecoderCounters, videoDecoderCounters);
if (failOnPlayerError && playerError != null) {
throw new Error(playerError);
}
+ logMetrics(audioDecoderCounters, videoDecoderCounters);
if (expectedPlayingTimeMs != EXPECTED_PLAYING_TIME_UNSET) {
long playingTimeToAssertMs = expectedPlayingTimeMs == EXPECTED_PLAYING_TIME_MEDIA_DURATION_MS
? sourceDurationMs : expectedPlayingTimeMs;
@@ -174,6 +178,8 @@
&& totalPlayingTimeMs <= maxAllowedActualPlayingTimeMs)
.isTrue();
}
+ // Make any additional assertions.
+ assertPassed(audioDecoderCounters, videoDecoderCounters);
}
// AnalyticsListener
@@ -216,20 +222,19 @@
// Internal logic
private boolean stopTest() {
- if (player == null) {
+ if (!isStarted()) {
return false;
}
actionHandler.removeCallbacksAndMessages(null);
sourceDurationMs = player.getDuration();
player.release();
- player = null;
// We post opening of the finished condition so that any events posted to the main thread as a
// result of player.release() are guaranteed to be handled before the test returns.
actionHandler.post(testFinished::open);
return true;
}
- protected DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(String userAgent) {
+ protected DrmSessionManager buildDrmSessionManager(String userAgent) {
// Do nothing. Interested subclasses may override.
return DrmSessionManager.getDummyDrmSessionManager();
}
@@ -252,13 +257,33 @@
}
protected abstract MediaSource buildSource(
- HostActivity host, String userAgent, DrmSessionManager<?> drmSessionManager);
+ HostActivity host,
+ String userAgent,
+ DrmSessionManager drmSessionManager,
+ FrameLayout overlayFrameLayout);
protected void onPlayerErrorInternal(ExoPlaybackException error) {
// Do nothing. Interested subclasses may override.
}
- protected void onTestFinished(DecoderCounters audioCounters, DecoderCounters videoCounters) {
- // Do nothing. Subclasses may override to add clean-up and assertions.
+ protected void logMetrics(DecoderCounters audioCounters, DecoderCounters videoCounters) {
+ // Do nothing. Subclasses may override to log metrics.
+ }
+
+ protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) {
+ // Do nothing. Subclasses may override to add additional assertions.
+ }
+
+ @EnsuresNonNullIf(
+ result = true,
+ expression = {"player", "actionHandler", "trackSelector", "surface"})
+ private boolean isStarted() {
+ if (player == null) {
+ return false;
+ }
+ Util.castNonNull(actionHandler);
+ Util.castNonNull(trackSelector);
+ Util.castNonNull(surface);
+ return true;
}
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
index 28cf8ba..2869c5b 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
@@ -17,14 +17,12 @@
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
import android.content.Context;
import android.os.HandlerThread;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.LoadControl;
@@ -33,14 +31,12 @@
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
-import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.MimeTypes;
@@ -54,62 +50,47 @@
/** Helper class to run an ExoPlayer test. */
public final class ExoPlayerTestRunner implements Player.EventListener, ActionSchedule.Callback {
+ /** A generic video {@link Format} which can be used to set up a {@link FakeMediaSource}. */
+ public static final Format VIDEO_FORMAT =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(800_000)
+ .setWidth(1280)
+ .setHeight(720)
+ .build();
+
+ /** A generic audio {@link Format} which can be used to set up a {@link FakeMediaSource}. */
+ public static final Format AUDIO_FORMAT =
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.AUDIO_AAC)
+ .setAverageBitrate(100_000)
+ .setChannelCount(2)
+ .setSampleRate(44100)
+ .build();
+
/**
* Builder to set-up a {@link ExoPlayerTestRunner}. Default fake implementations will be used for
* unset test properties.
*/
public static final class Builder {
-
- /** A generic video {@link Format} which can be used to set up media sources and renderers. */
- public static final Format VIDEO_FORMAT =
- Format.createVideoSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.VIDEO_H264,
- /* codecs= */ null,
- /* bitrate= */ 800_000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* width= */ 1280,
- /* height= */ 720,
- /* frameRate= */ Format.NO_VALUE,
- /* initializationData= */ null,
- /* drmInitData= */ null);
-
- /** A generic audio {@link Format} which can be used to set up media sources and renderers. */
- public static final Format AUDIO_FORMAT =
- Format.createAudioSampleFormat(
- /* id= */ null,
- /* sampleMimeType= */ MimeTypes.AUDIO_AAC,
- /* codecs= */ null,
- /* bitrate= */ 100_000,
- /* maxInputSize= */ Format.NO_VALUE,
- /* channelCount= */ 2,
- /* sampleRate= */ 44100,
- /* initializationData=*/ null,
- /* drmInitData= */ null,
- /* selectionFlags= */ 0,
- /* language= */ null);
-
- private Clock clock;
+ private final TestExoPlayer.Builder testPlayerBuilder;
private Timeline timeline;
private List<MediaSource> mediaSources;
- private Object manifest;
- private DefaultTrackSelector trackSelector;
- private LoadControl loadControl;
- private BandwidthMeter bandwidthMeter;
private Format[] supportedFormats;
- private Renderer[] renderers;
- private RenderersFactory renderersFactory;
+ private Object manifest;
private ActionSchedule actionSchedule;
private Player.EventListener eventListener;
private AnalyticsListener analyticsListener;
private Integer expectedPlayerEndedCount;
- private boolean useLazyPreparation;
+ private boolean pauseAtEndOfMediaItems;
private int initialWindowIndex;
private long initialPositionMs;
private boolean skipSettingMediaSources;
- public Builder() {
+ public Builder(Context context) {
+ testPlayerBuilder = new TestExoPlayer.Builder(context);
mediaSources = new ArrayList<>();
+ supportedFormats = new Format[] {VIDEO_FORMAT};
initialWindowIndex = C.INDEX_UNSET;
initialPositionMs = C.TIME_UNSET;
}
@@ -179,74 +160,10 @@
}
/**
- * Skips calling {@link com.google.android.exoplayer2.ExoPlayer#setMediaSources(List)} before
- * preparing. Calling this method is not allowed after calls to {@link
- * #setMediaSources(MediaSource...)}, {@link #setTimeline(Timeline)} and/or {@link
- * #setManifest(Object)}.
- *
- * @return This builder.
- */
- public Builder skipSettingMediaSources() {
- assertThat(timeline).isNull();
- assertThat(manifest).isNull();
- assertTrue(mediaSources.isEmpty());
- skipSettingMediaSources = true;
- return this;
- }
-
- /**
- * Sets whether to use lazy preparation.
- *
- * @param useLazyPreparation Whether to use lazy preparation.
- * @return This builder.
- */
- public Builder setUseLazyPreparation(boolean useLazyPreparation) {
- this.useLazyPreparation = useLazyPreparation;
- return this;
- }
-
- /**
- * Sets a {@link DefaultTrackSelector} to be used by the test runner. The default value is a
- * {@link DefaultTrackSelector} in its initial configuration.
- *
- * @param trackSelector A {@link DefaultTrackSelector} to be used by the test runner.
- * @return This builder.
- */
- public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
- this.trackSelector = trackSelector;
- return this;
- }
-
- /**
- * Sets a {@link LoadControl} to be used by the test runner. The default value is a
- * {@link DefaultLoadControl}.
- *
- * @param loadControl A {@link LoadControl} to be used by the test runner.
- * @return This builder.
- */
- public Builder setLoadControl(LoadControl loadControl) {
- this.loadControl = loadControl;
- return this;
- }
-
- /**
- * Sets the {@link BandwidthMeter} to be used by the test runner. The default value is a {@link
- * DefaultBandwidthMeter} in its default configuration.
- *
- * @param bandwidthMeter The {@link BandwidthMeter} to be used by the test runner.
- * @return This builder.
- */
- public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
- this.bandwidthMeter = bandwidthMeter;
- return this;
- }
-
- /**
* Sets a list of {@link Format}s to be used by a {@link FakeMediaSource} to create media
- * periods and for setting up a {@link FakeRenderer}. The default value is a single {@link
- * #VIDEO_FORMAT}. Note that this parameter doesn't have any influence if both a media source
- * with {@link #setMediaSources(MediaSource...)} and renderers with {@link
- * #setRenderers(Renderer...)} or {@link #setRenderersFactory(RenderersFactory)} are set.
+ * periods. The default value is a single {@link #VIDEO_FORMAT}. Note that this parameter
+ * doesn't have any influence if a media source with {@link #setMediaSources(MediaSource...)} is
+ * set.
*
* @param supportedFormats A list of supported {@link Format}s.
* @return This builder.
@@ -257,43 +174,92 @@
}
/**
- * Sets the {@link Renderer}s to be used by the test runner. The default value is a single
- * {@link FakeRenderer} supporting the formats set by {@link #setSupportedFormats(Format...)}.
- * Setting the renderers is not allowed after a call to
- * {@link #setRenderersFactory(RenderersFactory)}.
+ * Skips calling {@link com.google.android.exoplayer2.ExoPlayer#setMediaSources(List)} before
+ * preparing. Calling this method is not allowed after calls to {@link
+ * #setMediaSources(MediaSource...)}, {@link #setTimeline(Timeline)} and/or {@link
+ * #setManifest(Object)}.
*
- * @param renderers A list of {@link Renderer}s to be used by the test runner.
+ * @return This builder.
+ */
+ public Builder skipSettingMediaSources() {
+ assertThat(timeline).isNull();
+ assertThat(manifest).isNull();
+ assertThat(mediaSources).isEmpty();
+ skipSettingMediaSources = true;
+ return this;
+ }
+
+ /**
+ * @see TestExoPlayer.Builder#setUseLazyPreparation(boolean)
+ * @return This builder.
+ */
+ public Builder setUseLazyPreparation(boolean useLazyPreparation) {
+ testPlayerBuilder.setUseLazyPreparation(useLazyPreparation);
+ return this;
+ }
+
+ /**
+ * Sets whether to enable pausing at the end of media items.
+ *
+ * @param pauseAtEndOfMediaItems Whether to pause at the end of media items.
+ * @return This builder.
+ */
+ public Builder setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
+ this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
+ return this;
+ }
+
+ /**
+ * @see TestExoPlayer.Builder#setTrackSelector(DefaultTrackSelector)
+ * @return This builder.
+ */
+ public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
+ testPlayerBuilder.setTrackSelector(trackSelector);
+ return this;
+ }
+
+ /**
+ * @see TestExoPlayer.Builder#setLoadControl(LoadControl)
+ * @return This builder.
+ */
+ public Builder setLoadControl(LoadControl loadControl) {
+ testPlayerBuilder.setLoadControl(loadControl);
+ return this;
+ }
+
+ /**
+ * @see TestExoPlayer.Builder#setBandwidthMeter(BandwidthMeter)
+ * @return This builder.
+ */
+ public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
+ this.testPlayerBuilder.setBandwidthMeter(bandwidthMeter);
+ return this;
+ }
+
+ /**
+ * @see TestExoPlayer.Builder#setRenderers(Renderer...)
* @return This builder.
*/
public Builder setRenderers(Renderer... renderers) {
- assertThat(renderersFactory).isNull();
- this.renderers = renderers;
+ testPlayerBuilder.setRenderers(renderers);
return this;
}
/**
- * Sets the {@link RenderersFactory} to be used by the test runner. The default factory creates
- * all renderers set by {@link #setRenderers(Renderer...)}. Setting the renderer factory is not
- * allowed after a call to {@link #setRenderers(Renderer...)}.
- *
- * @param renderersFactory A {@link RenderersFactory} to be used by the test runner.
+ * @see TestExoPlayer.Builder#setRenderersFactory(RenderersFactory)
* @return This builder.
*/
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
- assertThat(renderers).isNull();
- this.renderersFactory = renderersFactory;
+ testPlayerBuilder.setRenderersFactory(renderersFactory);
return this;
}
/**
- * Sets the {@link Clock} to be used by the test runner. The default value is a {@link
- * AutoAdvancingFakeClock}.
- *
- * @param clock A {@link Clock} to be used by the test runner.
+ * @see TestExoPlayer.Builder#setClock(Clock)
* @return This builder.
*/
public Builder setClock(Clock clock) {
- this.clock = clock;
+ testPlayerBuilder.setClock(clock);
return this;
}
@@ -349,37 +315,9 @@
/**
* Builds an {@link ExoPlayerTestRunner} using the provided values or their defaults.
*
- * @param context The context.
* @return The built {@link ExoPlayerTestRunner}.
*/
- public ExoPlayerTestRunner build(Context context) {
- if (supportedFormats == null) {
- supportedFormats = new Format[] {VIDEO_FORMAT};
- }
- if (trackSelector == null) {
- trackSelector = new DefaultTrackSelector(context);
- }
- if (bandwidthMeter == null) {
- bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
- }
- if (renderersFactory == null) {
- if (renderers == null) {
- renderers = new Renderer[] {new FakeRenderer(supportedFormats)};
- }
- renderersFactory =
- (eventHandler,
- videoRendererEventListener,
- audioRendererEventListener,
- textRendererOutput,
- metadataRendererOutput,
- drmSessionManager) -> renderers;
- }
- if (loadControl == null) {
- loadControl = new DefaultLoadControl();
- }
- if (clock == null) {
- clock = new AutoAdvancingFakeClock();
- }
+ public ExoPlayerTestRunner build() {
if (mediaSources.isEmpty() && !skipSettingMediaSources) {
if (timeline == null) {
timeline = new FakeTimeline(/* windowCount= */ 1, manifest);
@@ -390,33 +328,24 @@
expectedPlayerEndedCount = 1;
}
return new ExoPlayerTestRunner(
- context,
- clock,
- initialWindowIndex,
- initialPositionMs,
+ testPlayerBuilder,
mediaSources,
skipSettingMediaSources,
- useLazyPreparation,
- renderersFactory,
- trackSelector,
- loadControl,
- bandwidthMeter,
+ initialWindowIndex,
+ initialPositionMs,
actionSchedule,
eventListener,
analyticsListener,
- expectedPlayerEndedCount);
+ expectedPlayerEndedCount,
+ pauseAtEndOfMediaItems);
}
}
- private final Context context;
- private final Clock clock;
+ private final TestExoPlayer.Builder playerBuilder;
+ private final List<MediaSource> mediaSources;
+ private final boolean skipSettingMediaSources;
private final int initialWindowIndex;
private final long initialPositionMs;
- private final List<MediaSource> mediaSources;
- private final RenderersFactory renderersFactory;
- private final DefaultTrackSelector trackSelector;
- private final LoadControl loadControl;
- private final BandwidthMeter bandwidthMeter;
@Nullable private final ActionSchedule actionSchedule;
@Nullable private final Player.EventListener eventListener;
@Nullable private final AnalyticsListener analyticsListener;
@@ -430,8 +359,7 @@
private final ArrayList<Integer> periodIndices;
private final ArrayList<Integer> discontinuityReasons;
private final ArrayList<Integer> playbackStates;
- private final boolean skipSettingMediaSources;
- private final boolean useLazyPreparation;
+ private final boolean pauseAtEndOfMediaItems;
private SimpleExoPlayer player;
private Exception exception;
@@ -439,45 +367,36 @@
private boolean playerWasPrepared;
private ExoPlayerTestRunner(
- Context context,
- Clock clock,
- int initialWindowIndex,
- long initialPositionMs,
+ TestExoPlayer.Builder playerBuilder,
List<MediaSource> mediaSources,
boolean skipSettingMediaSources,
- boolean useLazyPreparation,
- RenderersFactory renderersFactory,
- DefaultTrackSelector trackSelector,
- LoadControl loadControl,
- BandwidthMeter bandwidthMeter,
+ int initialWindowIndex,
+ long initialPositionMs,
@Nullable ActionSchedule actionSchedule,
@Nullable Player.EventListener eventListener,
@Nullable AnalyticsListener analyticsListener,
- int expectedPlayerEndedCount) {
- this.context = context;
- this.clock = clock;
- this.initialWindowIndex = initialWindowIndex;
- this.initialPositionMs = initialPositionMs;
+ int expectedPlayerEndedCount,
+ boolean pauseAtEndOfMediaItems) {
+ this.playerBuilder = playerBuilder;
this.mediaSources = mediaSources;
this.skipSettingMediaSources = skipSettingMediaSources;
- this.useLazyPreparation = useLazyPreparation;
- this.renderersFactory = renderersFactory;
- this.trackSelector = trackSelector;
- this.loadControl = loadControl;
- this.bandwidthMeter = bandwidthMeter;
+ this.initialWindowIndex = initialWindowIndex;
+ this.initialPositionMs = initialPositionMs;
this.actionSchedule = actionSchedule;
this.eventListener = eventListener;
this.analyticsListener = analyticsListener;
- this.timelines = new ArrayList<>();
- this.timelineChangeReasons = new ArrayList<>();
- this.periodIndices = new ArrayList<>();
- this.discontinuityReasons = new ArrayList<>();
- this.playbackStates = new ArrayList<>();
- this.endedCountDownLatch = new CountDownLatch(expectedPlayerEndedCount);
- this.actionScheduleFinishedCountDownLatch = new CountDownLatch(actionSchedule != null ? 1 : 0);
- this.playerThread = new HandlerThread("ExoPlayerTest thread");
+ timelines = new ArrayList<>();
+ timelineChangeReasons = new ArrayList<>();
+ periodIndices = new ArrayList<>();
+ discontinuityReasons = new ArrayList<>();
+ playbackStates = new ArrayList<>();
+ endedCountDownLatch = new CountDownLatch(expectedPlayerEndedCount);
+ actionScheduleFinishedCountDownLatch = new CountDownLatch(actionSchedule != null ? 1 : 0);
+ playerThread = new HandlerThread("ExoPlayerTest thread");
playerThread.start();
- this.handler = clock.createHandler(playerThread.getLooper(), /* callback= */ null);
+ handler =
+ playerBuilder.getClock().createHandler(playerThread.getLooper(), /* callback= */ null);
+ this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
}
// Called on the test thread to run the test.
@@ -504,16 +423,7 @@
handler.post(
() -> {
try {
- player =
- new SimpleExoPlayer.Builder(context, renderersFactory)
- .setTrackSelector(trackSelector)
- .setLoadControl(loadControl)
- .setBandwidthMeter(bandwidthMeter)
- .setAnalyticsCollector(new AnalyticsCollector(clock))
- .setClock(clock)
- .setUseLazyPreparation(useLazyPreparation)
- .setLooper(Looper.myLooper())
- .build();
+ player = playerBuilder.setLooper(Looper.myLooper()).build();
player.addListener(ExoPlayerTestRunner.this);
if (eventListener != null) {
player.addListener(eventListener);
@@ -521,9 +431,17 @@
if (analyticsListener != null) {
player.addAnalyticsListener(analyticsListener);
}
+ if (pauseAtEndOfMediaItems) {
+ player.setPauseAtEndOfMediaItems(true);
+ }
player.play();
if (actionSchedule != null) {
- actionSchedule.start(player, trackSelector, null, handler, ExoPlayerTestRunner.this);
+ actionSchedule.start(
+ player,
+ playerBuilder.getTrackSelector(),
+ /* surface= */ null,
+ handler,
+ /* callback= */ ExoPlayerTestRunner.this);
}
if (initialWindowIndex != C.INDEX_UNSET) {
player.seekTo(initialWindowIndex, initialPositionMs);
@@ -609,8 +527,7 @@
/**
* Asserts that the playback states reported by {@link
- * Player.EventListener#onPlayerStateChanged(boolean, int)} are equal to the provided playback
- * states.
+ * Player.EventListener#onPlaybackStateChanged(int)} are equal to the provided playback states.
*/
public void assertPlaybackStatesEqual(Integer... states) {
assertThat(playbackStates).containsExactlyElementsIn(Arrays.asList(states)).inOrder();
@@ -706,7 +623,7 @@
}
@Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
playbackStates.add(playbackState);
playerWasPrepared |= playbackState != Player.STATE_IDLE;
if (playbackState == Player.STATE_ENDED
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
index 050898a..dff69c1 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
@@ -28,7 +28,6 @@
import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
-import java.util.Arrays;
/**
* Assertion methods for {@link Extractor}.
@@ -43,7 +42,7 @@
}
private static final String DUMP_EXTENSION = ".dump";
- private static final String UNKNOWN_LENGTH_EXTENSION = ".unklen" + DUMP_EXTENSION;
+ private static final String UNKNOWN_LENGTH_EXTENSION = ".unknown_length" + DUMP_EXTENSION;
/**
* Asserts that {@link Extractor#sniff(ExtractorInput)} returns the {@code expectedResult} for a
@@ -53,11 +52,9 @@
* @param input The extractor input.
* @param expectedResult The expected return value.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
public static void assertSniff(
- Extractor extractor, FakeExtractorInput input, boolean expectedResult)
- throws IOException, InterruptedException {
+ Extractor extractor, FakeExtractorInput input, boolean expectedResult) throws IOException {
while (true) {
try {
assertThat(extractor.sniff(input)).isEqualTo(expectedResult);
@@ -83,17 +80,9 @@
* class which is to be tested.
* @param file The path to the input sample.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
- public static void assertBehavior(ExtractorFactory factory, String file)
- throws IOException, InterruptedException {
- // Check behavior prior to initialization.
- Extractor extractor = factory.create();
- extractor.seek(0, 0);
- extractor.release();
- // Assert output.
- byte[] fileData = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file);
- assertOutput(factory, file, fileData, ApplicationProvider.getApplicationContext());
+ public static void assertBehavior(ExtractorFactory factory, String file) throws IOException {
+ assertBehavior(factory, file, ApplicationProvider.getApplicationContext());
}
/**
@@ -111,17 +100,39 @@
* @param file The path to the input sample.
* @param context To be used to load the sample file.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
public static void assertBehavior(ExtractorFactory factory, String file, Context context)
- throws IOException, InterruptedException {
+ throws IOException {
+ assertBehavior(factory, file, context, file);
+ }
+
+ /**
+ * Asserts that an extractor behaves correctly given valid input data:
+ *
+ * <ul>
+ * <li>Calls {@link Extractor#seek(long, long)} and {@link Extractor#release()} without calling
+ * {@link Extractor#init(ExtractorOutput)} to check these calls do not fail.
+ * <li>Calls {@link #assertOutput(Extractor, String, byte[], Context, boolean, boolean, boolean,
+ * boolean)} with all possible combinations of "simulate" parameters.
+ * </ul>
+ *
+ * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor}
+ * class which is to be tested.
+ * @param file The path to the input sample.
+ * @param context To be used to load the sample file.
+ * @param dumpFilesPrefix The dump files prefix appended to the dump files path.
+ * @throws IOException If reading from the input fails.
+ */
+ public static void assertBehavior(
+ ExtractorFactory factory, String file, Context context, String dumpFilesPrefix)
+ throws IOException {
// Check behavior prior to initialization.
Extractor extractor = factory.create();
extractor.seek(0, 0);
extractor.release();
// Assert output.
byte[] fileData = TestUtil.getByteArray(context, file);
- assertOutput(factory, file, fileData, context);
+ assertOutput(factory, dumpFilesPrefix, fileData, context);
}
/**
@@ -132,34 +143,31 @@
*
* @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor}
* class which is to be tested.
- * @param file The path to the input sample.
+ * @param dumpFilesPrefix The dump files prefix appended to the dump files path.
* @param data Content of the input file.
* @param context To be used to load the sample file.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
public static void assertOutput(
- ExtractorFactory factory, String file, byte[] data, Context context)
- throws IOException, InterruptedException {
- assertOutput(factory.create(), file, data, context, true, false, false, false);
- assertOutput(factory.create(), file, data, context, true, false, false, true);
- assertOutput(factory.create(), file, data, context, true, false, true, false);
- assertOutput(factory.create(), file, data, context, true, false, true, true);
- assertOutput(factory.create(), file, data, context, true, true, false, false);
- assertOutput(factory.create(), file, data, context, true, true, false, true);
- assertOutput(factory.create(), file, data, context, true, true, true, false);
- assertOutput(factory.create(), file, data, context, true, true, true, true);
- assertOutput(factory.create(), file, data, context, false, false, false, false);
+ ExtractorFactory factory, String dumpFilesPrefix, byte[] data, Context context)
+ throws IOException {
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, false, false, false);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, false, false, true);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, false, true, false);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, false, true, true);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, true, false, false);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, true, false, true);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, true, true, false);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, true, true, true, true);
+ assertOutput(factory.create(), dumpFilesPrefix, data, context, false, false, false, false);
}
/**
- * Asserts that {@code extractor} consumes {@code sampleFile} successfully and its output equals
- * to a prerecorded output dump file with the name {@code sampleFile} + "{@value
- * #DUMP_EXTENSION}". If {@code simulateUnknownLength} is true and {@code sampleFile} + "{@value
- * #UNKNOWN_LENGTH_EXTENSION}" exists, it's preferred.
+ * Asserts that {@code extractor} consumes {@code data} successfully and that its output for
+ * various initial seek times and for a known and unknown length matches prerecorded dump files.
*
* @param extractor The {@link Extractor} to be tested.
- * @param file The path to the input sample.
+ * @param dumpFilesPrefix The dump files prefix appended to the dump files path.
* @param data Content of the input file.
* @param context To be used to load the sample file.
* @param sniffFirst Whether to sniff the data by calling {@link Extractor#sniff(ExtractorInput)}
@@ -169,18 +177,17 @@
* @param simulatePartialReads Whether to simulate partial reads.
* @return The {@link FakeExtractorOutput} used in the test.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
public static FakeExtractorOutput assertOutput(
Extractor extractor,
- String file,
+ String dumpFilesPrefix,
byte[] data,
Context context,
boolean sniffFirst,
boolean simulateIOErrors,
boolean simulateUnknownLength,
boolean simulatePartialReads)
- throws IOException, InterruptedException {
+ throws IOException {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data)
.setSimulateIOErrors(simulateIOErrors)
.setSimulateUnknownLength(simulateUnknownLength)
@@ -192,24 +199,24 @@
}
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true);
- if (simulateUnknownLength && assetExists(context, file + UNKNOWN_LENGTH_EXTENSION)) {
- extractorOutput.assertOutput(context, file + UNKNOWN_LENGTH_EXTENSION);
+ if (simulateUnknownLength) {
+ extractorOutput.assertOutput(context, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION);
} else {
- extractorOutput.assertOutput(context, file + ".0" + DUMP_EXTENSION);
+ extractorOutput.assertOutput(context, dumpFilesPrefix + ".0" + DUMP_EXTENSION);
}
// Seeking to (timeUs=0, position=0) should always work, and cause the same data to be output.
extractorOutput.clearTrackOutputs();
input.reset();
consumeTestData(extractor, input, /* timeUs= */ 0, extractorOutput, false);
- if (simulateUnknownLength && assetExists(context, file + UNKNOWN_LENGTH_EXTENSION)) {
- extractorOutput.assertOutput(context, file + UNKNOWN_LENGTH_EXTENSION);
+ if (simulateUnknownLength) {
+ extractorOutput.assertOutput(context, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION);
} else {
- extractorOutput.assertOutput(context, file + ".0" + DUMP_EXTENSION);
+ extractorOutput.assertOutput(context, dumpFilesPrefix + ".0" + DUMP_EXTENSION);
}
// If the SeekMap is seekable, test seeking in the stream.
- SeekMap seekMap = extractorOutput.seekMap;
+ SeekMap seekMap = Assertions.checkNotNull(extractorOutput.seekMap);
if (seekMap.isSeekable()) {
long durationUs = seekMap.getDurationUs();
for (int j = 0; j < 4; j++) {
@@ -219,7 +226,7 @@
input.reset();
input.setPosition((int) position);
consumeTestData(extractor, input, timeUs, extractorOutput, false);
- extractorOutput.assertOutput(context, file + '.' + j + DUMP_EXTENSION);
+ extractorOutput.assertOutput(context, dumpFilesPrefix + '.' + j + DUMP_EXTENSION);
if (durationUs == C.TIME_UNSET) {
break;
}
@@ -239,7 +246,6 @@
* @param context To be used to load the sample file.
* @param expectedThrowable Expected {@link Throwable} class.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
* @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)
*/
public static void assertThrows(
@@ -247,7 +253,7 @@
String sampleFile,
Context context,
Class<? extends Throwable> expectedThrowable)
- throws IOException, InterruptedException {
+ throws IOException {
byte[] fileData = TestUtil.getByteArray(context, sampleFile);
assertThrows(factory, fileData, expectedThrowable);
}
@@ -261,12 +267,11 @@
* @param fileData Content of the input file.
* @param expectedThrowable Expected {@link Throwable} class.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
* @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)
*/
private static void assertThrows(
ExtractorFactory factory, byte[] fileData, Class<? extends Throwable> expectedThrowable)
- throws IOException, InterruptedException {
+ throws IOException {
assertThrows(factory.create(), fileData, expectedThrowable, false, false, false);
assertThrows(factory.create(), fileData, expectedThrowable, true, false, false);
assertThrows(factory.create(), fileData, expectedThrowable, false, true, false);
@@ -278,7 +283,7 @@
}
/**
- * Asserts {@code extractor} throws {@code expectedThrowable} while consuming {@code sampleFile}.
+ * Asserts {@code extractor} throws {@code expectedThrowable} while consuming {@code fileData}.
*
* @param extractor The {@link Extractor} to be tested.
* @param fileData Content of the input file.
@@ -287,7 +292,6 @@
* @param simulateUnknownLength If true simulates unknown input length.
* @param simulatePartialReads If true simulates partial reads.
* @throws IOException If reading from the input fails.
- * @throws InterruptedException If interrupted while reading from the input.
*/
private static void assertThrows(
Extractor extractor,
@@ -296,7 +300,7 @@
boolean simulateIOErrors,
boolean simulateUnknownLength,
boolean simulatePartialReads)
- throws IOException, InterruptedException {
+ throws IOException {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData)
.setSimulateIOErrors(simulateIOErrors)
.setSimulateUnknownLength(simulateUnknownLength)
@@ -314,17 +318,22 @@
private ExtractorAsserts() {}
- private static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
- long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException {
+ private static FakeExtractorOutput consumeTestData(
+ Extractor extractor, FakeExtractorInput input, long timeUs, boolean retryFromStartIfLive)
+ throws IOException {
FakeExtractorOutput output = new FakeExtractorOutput();
extractor.init(output);
consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive);
return output;
}
- private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs,
- FakeExtractorOutput output, boolean retryFromStartIfLive)
- throws IOException, InterruptedException {
+ private static void consumeTestData(
+ Extractor extractor,
+ FakeExtractorInput input,
+ long timeUs,
+ FakeExtractorOutput output,
+ boolean retryFromStartIfLive)
+ throws IOException {
extractor.seek(input.getPosition(), timeUs);
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
@@ -356,12 +365,4 @@
}
}
}
-
- private static boolean assetExists(Context context, String fileName) throws IOException {
- int i = fileName.lastIndexOf('/');
- String path = i >= 0 ? fileName.substring(0, i) : "";
- String file = i >= 0 ? fileName.substring(i + 1) : fileName;
- return Arrays.asList(context.getResources().getAssets().list(path)).contains(file);
- }
-
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java
index 0fef8db..9e9642c 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSet.java
@@ -23,6 +23,7 @@
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.util.Util;
import java.util.Random;
/**
@@ -92,9 +93,8 @@
checkInBounds();
String uri = dataSet.getUri(trackGroupIndex);
int chunkIndex = (int) getCurrentIndex();
- Segment fakeDataChunk = dataSet.getData(uri).getSegments().get(chunkIndex);
- return new DataSpec(
- Uri.parse(uri), fakeDataChunk.byteOffset, fakeDataChunk.length, /* key= */ null);
+ Segment fakeDataChunk = Util.castNonNull(dataSet.getData(uri)).getSegments().get(chunkIndex);
+ return new DataSpec(Uri.parse(uri), fakeDataChunk.byteOffset, fakeDataChunk.length);
}
@Override
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
index 21f2a04..7d74cc2 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
@@ -28,9 +28,12 @@
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting a
@@ -44,7 +47,7 @@
@Nullable private final TransferListener transferListener;
private final long durationUs;
- private Callback callback;
+ @MonotonicNonNull private Callback callback;
private ChunkSampleStream<FakeChunkSource>[] sampleStreams;
private SequenceableLoader sequenceableLoader;
@@ -81,9 +84,9 @@
@Override
@SuppressWarnings("unchecked")
public long selectTracks(
- TrackSelection[] selections,
+ @NullableType TrackSelection[] selections,
boolean[] mayRetainStreamFlags,
- SampleStream[] streams,
+ @NullableType SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs) {
long returnPositionUs = super.selectTracks(selections, mayRetainStreamFlags, streams,
@@ -94,7 +97,8 @@
validStreams.add((ChunkSampleStream<FakeChunkSource>) stream);
}
}
- this.sampleStreams = validStreams.toArray(newSampleStreamArray(validStreams.size()));
+ sampleStreams = newSampleStreamArray(validStreams.size());
+ validStreams.toArray(sampleStreams);
this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return returnPositionUs;
}
@@ -138,7 +142,7 @@
@Override
protected SampleStream createSampleStream(
- TrackSelection trackSelection, EventDispatcher eventDispatcher) {
+ long positionUs, TrackSelection trackSelection, EventDispatcher eventDispatcher) {
FakeChunkSource chunkSource =
chunkSourceFactory.createChunkSource(trackSelection, durationUs, transferListener);
return new ChunkSampleStream<>(
@@ -148,7 +152,7 @@
chunkSource,
/* callback= */ this,
allocator,
- /* positionUs= */ 0,
+ positionUs,
/* drmSessionManager= */ DrmSessionManager.getDummyDrmSessionManager(),
new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3),
eventDispatcher);
@@ -162,7 +166,7 @@
@Override
public void onContinueLoadingRequested(ChunkSampleStream<FakeChunkSource> source) {
- callback.onContinueLoadingRequested(this);
+ Assertions.checkStateNotNull(callback).onContinueLoadingRequested(this);
}
// We won't assign the array to a variable that erases the generic type, and then write into it.
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java
index 0d97b7a..0f171dd 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaSource.java
@@ -23,6 +23,7 @@
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Util;
/**
* Fake {@link MediaSource} that provides a given timeline. Creating the period returns a
@@ -47,7 +48,7 @@
Allocator allocator,
EventDispatcher eventDispatcher,
@Nullable TransferListener transferListener) {
- Period period = timeline.getPeriodByUid(id.periodUid, new Period());
+ Period period = Util.castNonNull(getTimeline()).getPeriodByUid(id.periodUid, new Period());
return new FakeAdaptiveMediaPeriod(
trackGroupArray,
eventDispatcher,
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
new file mode 100644
index 0000000..5ed4e5b
--- /dev/null
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.testutil;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.audio.AudioRendererEventListener;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
+
+/** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_AUDIO}. */
+public class FakeAudioRenderer extends FakeRenderer {
+
+ private final AudioRendererEventListener.EventDispatcher eventDispatcher;
+ private final DecoderCounters decoderCounters;
+ private boolean notifiedAudioSessionId;
+
+ public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
+ super(C.TRACK_TYPE_AUDIO);
+ eventDispatcher = new AudioRendererEventListener.EventDispatcher(handler, eventListener);
+ decoderCounters = new DecoderCounters();
+ }
+
+ @Override
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ super.onEnabled(joining, mayRenderStartOfStream);
+ eventDispatcher.enabled(decoderCounters);
+ notifiedAudioSessionId = false;
+ }
+
+ @Override
+ protected void onDisabled() {
+ super.onDisabled();
+ eventDispatcher.disabled(decoderCounters);
+ }
+
+ @Override
+ protected void onFormatChanged(Format format) {
+ eventDispatcher.inputFormatChanged(format);
+ eventDispatcher.decoderInitialized(
+ /* decoderName= */ "fake.audio.decoder",
+ /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
+ /* initializationDurationMs= */ 0);
+ }
+
+ @Override
+ protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
+ boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
+ if (shouldProcess && !notifiedAudioSessionId) {
+ eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
+ notifiedAudioSessionId = true;
+ }
+ return shouldProcess;
+ }
+}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java
index aab221c..24a6efb 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java
@@ -31,8 +31,8 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
-import java.io.IOException;
import java.util.List;
/**
@@ -93,7 +93,7 @@
}
@Override
- public void maybeThrowError() throws IOException {
+ public void maybeThrowError() {
// Do nothing.
}
@@ -128,9 +128,10 @@
long endTimeUs = startTimeUs + dataSet.getChunkDuration(chunkIndex);
int trackGroupIndex = trackSelection.getIndexInTrackGroup(trackSelection.getSelectedIndex());
String uri = dataSet.getUri(trackGroupIndex);
- Segment fakeDataChunk = dataSet.getData(uri).getSegments().get(chunkIndex);
- DataSpec dataSpec = new DataSpec(Uri.parse(uri), fakeDataChunk.byteOffset,
- fakeDataChunk.length, null);
+ Segment fakeDataChunk =
+ Assertions.checkStateNotNull(dataSet.getData(uri)).getSegments().get(chunkIndex);
+ DataSpec dataSpec =
+ new DataSpec(Uri.parse(uri), fakeDataChunk.byteOffset, fakeDataChunk.length);
int trackType = MimeTypes.getTrackType(selectedFormat.sampleMimeType);
out.chunk = new SingleSampleMediaChunk(dataSource, dataSpec, selectedFormat,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs,
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
index a591546..c3c4d5c 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
@@ -18,6 +18,7 @@
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import java.util.ArrayList;
@@ -90,7 +91,7 @@
}
@Override
- public HandlerWrapper createHandler(Looper looper, Callback callback) {
+ public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) {
return new ClockHandler(looper, callback);
}
@@ -119,7 +120,7 @@
private final long postTime;
private final HandlerWrapper handler;
- private final Runnable runnable;
+ @Nullable private final Runnable runnable;
private final int message;
public HandlerMessageData(long postTime, HandlerWrapper handler, Runnable runnable) {
@@ -155,7 +156,7 @@
private final android.os.Handler handler;
- public ClockHandler(Looper looper, Callback callback) {
+ public ClockHandler(Looper looper, @Nullable Callback callback) {
handler = new android.os.Handler(looper, callback);
}
@@ -170,7 +171,7 @@
}
@Override
- public Message obtainMessage(int what, Object obj) {
+ public Message obtainMessage(int what, @Nullable Object obj) {
return handler.obtainMessage(what, obj);
}
@@ -180,7 +181,7 @@
}
@Override
- public Message obtainMessage(int what, int arg1, int arg2, Object obj) {
+ public Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj) {
return handler.obtainMessage(what, arg1, arg2, obj);
}
@@ -200,7 +201,7 @@
}
@Override
- public void removeCallbacksAndMessages(Object token) {
+ public void removeCallbacksAndMessages(@Nullable Object token) {
handler.removeCallbacksAndMessages(token);
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSet.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSet.java
index 286ef15..13cc6b6 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSet.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSet.java
@@ -89,24 +89,28 @@
public boolean exceptionCleared;
public int bytesRead;
- private Segment(byte[] data, Segment previousSegment) {
+ private Segment(byte[] data, @Nullable Segment previousSegment) {
this(data, data.length, null, null, previousSegment);
}
- private Segment(int length, Segment previousSegment) {
+ private Segment(int length, @Nullable Segment previousSegment) {
this(null, length, null, null, previousSegment);
}
- private Segment(IOException exception, Segment previousSegment) {
+ private Segment(IOException exception, @Nullable Segment previousSegment) {
this(null, 0, exception, null, previousSegment);
}
- private Segment(Runnable action, Segment previousSegment) {
+ private Segment(Runnable action, @Nullable Segment previousSegment) {
this(null, 0, null, action, previousSegment);
}
- private Segment(@Nullable byte[] data, int length, @Nullable IOException exception,
- @Nullable Runnable action, Segment previousSegment) {
+ private Segment(
+ @Nullable byte[] data,
+ int length,
+ @Nullable IOException exception,
+ @Nullable Runnable action,
+ @Nullable Segment previousSegment) {
this.exception = exception;
this.action = action;
this.data = data;
@@ -125,16 +129,17 @@
}
- /** Uri of the data or null if this is the default FakeData. */
- public final Uri uri;
- private final ArrayList<Segment> segments;
private final FakeDataSet dataSet;
+ /** Uri of the data or null if this is the default FakeData. */
+ @Nullable public final Uri uri;
+
+ private final ArrayList<Segment> segments;
private boolean simulateUnknownLength;
- private FakeData(FakeDataSet dataSet, Uri uri) {
+ private FakeData(FakeDataSet dataSet, @Nullable Uri uri) {
+ this.dataSet = dataSet;
this.uri = uri;
this.segments = new ArrayList<>();
- this.dataSet = dataSet;
}
/** Returns the {@link FakeDataSet} this FakeData belongs to. */
@@ -157,7 +162,7 @@
* Appends to the underlying data.
*/
public FakeData appendReadData(byte[] data) {
- Assertions.checkState(data != null && data.length > 0);
+ Assertions.checkState(data.length > 0);
segments.add(new Segment(data, getLastSegment()));
return this;
}
@@ -213,6 +218,7 @@
return simulateUnknownLength;
}
+ @Nullable
private Segment getLastSegment() {
int count = segments.size();
return count > 0 ? segments.get(count - 1) : null;
@@ -221,7 +227,7 @@
}
private final HashMap<Uri, FakeData> dataMap;
- private FakeData defaultData;
+ @Nullable private FakeData defaultData;
public FakeDataSet() {
dataMap = new HashMap<>();
@@ -266,13 +272,15 @@
}
/** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */
+ @Nullable
public FakeData getData(String uri) {
return getData(Uri.parse(uri));
}
/** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */
+ @Nullable
public FakeData getData(Uri uri) {
- FakeData data = dataMap.get(uri);
+ @Nullable FakeData data = dataMap.get(uri);
return data != null ? data : defaultData;
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
index ab7c5be..c55dc16 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil;
import android.net.Uri;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
@@ -24,8 +25,10 @@
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
@@ -38,7 +41,7 @@
*/
public static class Factory implements DataSource.Factory {
- protected FakeDataSet fakeDataSet;
+ protected @MonotonicNonNull FakeDataSet fakeDataSet;
protected boolean isNetwork;
public final Factory setFakeDataSet(FakeDataSet fakeDataSet) {
@@ -53,17 +56,17 @@
@Override
public FakeDataSource createDataSource() {
- return new FakeDataSource(fakeDataSet, isNetwork);
+ return new FakeDataSource(Assertions.checkStateNotNull(fakeDataSet), isNetwork);
}
}
private final FakeDataSet fakeDataSet;
private final ArrayList<DataSpec> openedDataSpecs;
- private Uri uri;
+ @Nullable private Uri uri;
private boolean openCalled;
private boolean sourceOpened;
- private FakeData fakeData;
+ @Nullable private FakeData fakeData;
private int currentSegmentIndex;
private long bytesRemaining;
@@ -96,10 +99,11 @@
openedDataSpecs.add(dataSpec);
transferInitializing(dataSpec);
- fakeData = fakeDataSet.getData(uri.toString());
+ FakeData fakeData = fakeDataSet.getData(dataSpec.uri.toString());
if (fakeData == null) {
throw new IOException("Data not found: " + dataSpec.uri);
}
+ this.fakeData = fakeData;
long totalLength = 0;
for (Segment segment : fakeData.getSegments()) {
@@ -145,6 +149,7 @@
public final int read(byte[] buffer, int offset, int readLength) throws IOException {
Assertions.checkState(sourceOpened);
while (true) {
+ FakeData fakeData = Util.castNonNull(this.fakeData);
if (currentSegmentIndex == fakeData.getSegments().size() || bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
@@ -152,13 +157,13 @@
if (current.isErrorSegment()) {
if (!current.exceptionCleared) {
current.exceptionThrown = true;
- throw (IOException) current.exception.fillInStackTrace();
+ throw (IOException) Util.castNonNull(current.exception).fillInStackTrace();
} else {
currentSegmentIndex++;
}
} else if (current.isActionSegment()) {
currentSegmentIndex++;
- current.action.run();
+ Util.castNonNull(current.action).run();
} else {
// Read at most bytesRemaining.
readLength = (int) Math.min(readLength, bytesRemaining);
@@ -182,12 +187,13 @@
}
@Override
+ @Nullable
public final Uri getUri() {
return uri;
}
@Override
- public final void close() throws IOException {
+ public final void close() {
Assertions.checkState(openCalled);
openCalled = false;
uri = null;
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
index 74a554d..5cefbff 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java
@@ -20,12 +20,19 @@
import android.content.Context;
import android.util.SparseArray;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.util.Assertions;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A fake {@link ExtractorOutput}.
@@ -33,19 +40,34 @@
public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpable {
/**
- * If true, makes {@link #assertOutput(Context, String)} method write the output to the dump file,
- * rather than validating that the output matches what the dump file already contains.
+ * Possible actions to take with the dumps generated from this {@code FakeExtractorOutput} in
+ * {@link #assertOutput(Context, String)}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ flag = true,
+ value = {COMPARE_WITH_EXISTING, WRITE_TO_LOCAL, WRITE_TO_DEVICE})
+ private @interface DumpFilesAction {}
+ /** Compare output with existing dump file. */
+ private static final int COMPARE_WITH_EXISTING = 0;
+ /**
+ * Write output to the project folder {@code testdata/src/test/assets}.
*
* <p>Enabling this option works when tests are run in Android Studio. It may not work when the
* tests are run in another environment.
*/
- private static final boolean WRITE_DUMP = false;
+ private static final int WRITE_TO_LOCAL = 1;
+ /** Write output to folder {@code /storage/emulated/0/Android/data} of device. */
+ private static final int WRITE_TO_DEVICE = 1 << 1;
+
+ @DumpFilesAction private static final int DUMP_FILE_ACTION = COMPARE_WITH_EXISTING;
public final SparseArray<FakeTrackOutput> trackOutputs;
public int numberOfTracks;
public boolean tracksEnded;
- public SeekMap seekMap;
+ public @MonotonicNonNull SeekMap seekMap;
public FakeExtractorOutput() {
trackOutputs = new SparseArray<>();
@@ -53,7 +75,7 @@
@Override
public FakeTrackOutput track(int id, int type) {
- FakeTrackOutput output = trackOutputs.get(id);
+ @Nullable FakeTrackOutput output = trackOutputs.get(id);
if (output == null) {
assertThat(tracksEnded).isFalse();
numberOfTracks++;
@@ -92,46 +114,39 @@
}
}
- public void assertEquals(FakeExtractorOutput expected) {
- assertThat(numberOfTracks).isEqualTo(expected.numberOfTracks);
- assertThat(tracksEnded).isEqualTo(expected.tracksEnded);
- if (expected.seekMap == null) {
- assertThat(seekMap).isNull();
- } else {
- // TODO: Bulk up this check if possible.
- assertThat(seekMap).isNotNull();
- assertThat(seekMap.getClass()).isEqualTo(expected.seekMap.getClass());
- assertThat(seekMap.isSeekable()).isEqualTo(expected.seekMap.isSeekable());
- assertThat(seekMap.getSeekPoints(0)).isEqualTo(expected.seekMap.getSeekPoints(0));
- }
- for (int i = 0; i < numberOfTracks; i++) {
- assertThat(trackOutputs.keyAt(i)).isEqualTo(expected.trackOutputs.keyAt(i));
- trackOutputs.valueAt(i).assertEquals(expected.trackOutputs.valueAt(i));
- }
- }
-
/**
* Asserts that dump of this {@link FakeExtractorOutput} is equal to expected dump which is read
* from {@code dumpFile}.
*
* <p>If assertion fails because of an intended change in the output or a new dump file needs to
- * be created, set {@link #WRITE_DUMP} flag to true and run the test again. Instead of assertion,
- * actual dump will be written to {@code dumpFile}. This new dump file needs to be copied to the
- * project, {@code library/src/androidTest/assets} folder manually.
+ * be created, set {@link #DUMP_FILE_ACTION} to {@link #WRITE_TO_LOCAL} for local tests and to
+ * {@link #WRITE_TO_DEVICE} for instrumentation tests, and run the test again. Instead of
+ * assertion, actual dump will be written to {@code dumpFile}. For instrumentation tests, this new
+ * dump file needs to be copied to the project {@code testdata/src/test/assets} folder manually.
*/
public void assertOutput(Context context, String dumpFile) throws IOException {
String actual = new Dumper().add(this).toString();
- if (WRITE_DUMP) {
- File file = new File(System.getProperty("user.dir"), "src/test/assets");
+ if (DUMP_FILE_ACTION == COMPARE_WITH_EXISTING) {
+ String expected = TestUtil.getString(context, dumpFile);
+ assertWithMessage(
+ "Extractor output doesn't match golden file: %s\n"
+ + "To update the golden, change FakeExtractorOutput#DUMP_FILE_ACTION to"
+ + " WRITE_TO_LOCAL (for Robolectric tests) or WRITE_TO_DEVICE (for"
+ + " instrumentation tests) and re-run the test.",
+ dumpFile)
+ .that(actual)
+ .isEqualTo(expected);
+ } else {
+ File file =
+ DUMP_FILE_ACTION == WRITE_TO_LOCAL
+ ? new File(System.getProperty("user.dir"), "../../testdata/src/test/assets")
+ : context.getExternalFilesDir(null);
file = new File(file, dumpFile);
- file.getParentFile().mkdirs();
+ Assertions.checkStateNotNull(file.getParentFile()).mkdirs();
PrintWriter out = new PrintWriter(file);
out.print(actual);
out.close();
- } else {
- String expected = TestUtil.getString(context, dumpFile);
- assertWithMessage(dumpFile).that(actual).isEqualTo(expected);
}
}
@@ -142,8 +157,18 @@
.startBlock("seekMap")
.add("isSeekable", seekMap.isSeekable())
.addTime("duration", seekMap.getDurationUs())
- .add("getPosition(0)", seekMap.getSeekPoints(0))
- .endBlock();
+ .add("getPosition(0)", seekMap.getSeekPoints(0));
+ if (seekMap.isSeekable()) {
+ dumper.add("getPosition(1)", seekMap.getSeekPoints(1));
+ if (seekMap.getDurationUs() != C.TIME_UNSET) {
+ // Dump seek points at the mid point and duration.
+ long durationUs = seekMap.getDurationUs();
+ long midPointUs = durationUs / 2;
+ dumper.add("getPosition(" + midPointUs + ")", seekMap.getSeekPoints(midPointUs));
+ dumper.add("getPosition(" + durationUs + ")", seekMap.getSeekPoints(durationUs));
+ }
+ }
+ dumper.endBlock();
}
dumper.add("numberOfTracks", numberOfTracks);
for (int i = 0; i < numberOfTracks; i++) {
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java
index fd7be24..0286531 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java
@@ -22,7 +22,6 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
-import java.io.IOException;
/** Fake {@link MediaChunk}. */
public final class FakeMediaChunk extends MediaChunk {
@@ -51,7 +50,7 @@
}
@Override
- public void load() throws IOException, InterruptedException {
+ public void load() {
// Do nothing.
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunkIterator.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunkIterator.java
index 6dce2b5..2e9fdeb 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunkIterator.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunkIterator.java
@@ -45,11 +45,7 @@
@Override
public DataSpec getDataSpec() {
checkInBounds();
- return new DataSpec(
- Uri.EMPTY,
- /* absoluteStreamPosition= */ 0,
- chunkLengths[(int) getCurrentIndex()],
- /* key= */ null);
+ return new DataSpec(Uri.EMPTY, /* position= */ 0, chunkLengths[(int) getCurrentIndex()]);
}
@Override
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java
index 009afd1..419c8c5 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java
@@ -15,15 +15,14 @@
*/
package com.google.android.exoplayer2.testutil;
-import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.util.MediaClock;
/** Fake abstract {@link Renderer} which is also a {@link MediaClock}. */
public abstract class FakeMediaClockRenderer extends FakeRenderer implements MediaClock {
- public FakeMediaClockRenderer(Format... expectedFormats) {
- super(expectedFormats);
+ public FakeMediaClockRenderer(int trackType) {
+ super(trackType);
}
@Override
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java
index ed71c4e..03a52cf 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaPeriod.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* Fake {@link MediaPeriod} that provides tracks from the given {@link TrackGroupArray}. Selecting
@@ -149,8 +150,12 @@
}
@Override
- public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
- SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
+ public long selectTracks(
+ @NullableType TrackSelection[] selections,
+ boolean[] mayRetainStreamFlags,
+ @NullableType SampleStream[] streams,
+ boolean[] streamResetFlags,
+ long positionUs) {
assertThat(prepared).isTrue();
sampleStreams.clear();
int rendererCount = selections.length;
@@ -166,7 +171,7 @@
int indexInTrackGroup = selection.getIndexInTrackGroup(selection.getSelectedIndex());
assertThat(indexInTrackGroup).isAtLeast(0);
assertThat(indexInTrackGroup).isLessThan(trackGroup.length);
- streams[i] = createSampleStream(selection, eventDispatcher);
+ streams[i] = createSampleStream(positionUs, selection, eventDispatcher);
sampleStreams.add(streams[i]);
streamResetFlags[i] = true;
}
@@ -236,38 +241,37 @@
/**
* Creates a sample stream for the provided selection.
*
+ * @param positionUs The position at which the tracks were selected, in microseconds.
* @param selection A selection of tracks.
* @param eventDispatcher A dispatcher for events that should be used by the sample stream.
* @return A {@link SampleStream} for this selection.
*/
protected SampleStream createSampleStream(
- TrackSelection selection, EventDispatcher eventDispatcher) {
+ long positionUs, TrackSelection selection, EventDispatcher eventDispatcher) {
return new FakeSampleStream(
selection.getSelectedFormat(),
eventDispatcher,
- FakeSampleStream.SINGLE_SAMPLE_THEN_END_OF_STREAM,
- /* timeUsIncrement= */ 0);
+ positionUs,
+ /* timeUsIncrement= */ 0,
+ FakeSampleStream.SINGLE_SAMPLE_THEN_END_OF_STREAM);
}
/**
* Seeks inside the given sample stream.
*
* @param sampleStream A sample stream that was created by a call to {@link
- * #createSampleStream(TrackSelection, EventDispatcher)}.
+ * #createSampleStream(long, TrackSelection, EventDispatcher)}.
* @param positionUs The position to seek to, in microseconds.
*/
protected void seekSampleStream(SampleStream sampleStream, long positionUs) {
- if (positionUs == 0) {
- // When seeking back to 0, queue our single sample at time 0 again.
- ((FakeSampleStream) sampleStream)
- .resetSampleStreamItems(
- FakeSampleStream.SINGLE_SAMPLE_THEN_END_OF_STREAM, /* timeUs= */ 0);
- }
+ // Queue a single sample from the seek position again.
+ ((FakeSampleStream) sampleStream)
+ .resetSampleStreamItems(positionUs, FakeSampleStream.SINGLE_SAMPLE_THEN_END_OF_STREAM);
}
private void finishPreparation() {
prepared = true;
- prepareCallback.onPrepared(this);
+ Util.castNonNull(prepareCallback).onPrepared(this);
eventDispatcher.loadCompleted(
FAKE_DATA_SPEC,
FAKE_DATA_SPEC.uri,
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
index e4bc539..61cc61e 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
@@ -43,6 +43,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Fake {@link MediaSource} that provides a given timeline. Creating the period will return a {@link
@@ -73,17 +74,17 @@
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private final ArrayList<MediaPeriodId> createdMediaPeriods;
- protected Timeline timeline;
+ private @MonotonicNonNull Timeline timeline;
private boolean preparedSource;
private boolean releasedSource;
- private Handler sourceInfoRefreshHandler;
+ @Nullable private Handler sourceInfoRefreshHandler;
@Nullable private TransferListener transferListener;
/**
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with a
* {@link TrackGroupArray} using the given {@link Format}s. The provided {@link Timeline} may be
* null to prevent an immediate source info refresh message when preparing the media source. It
- * can be manually set later using {@link #setNewSourceInfo(Timeline, Object)}.
+ * can be manually set later using {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(@Nullable Timeline timeline, Format... formats) {
this(timeline, buildTrackGroupArray(formats));
@@ -93,24 +94,33 @@
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with the
* given {@link TrackGroupArray}. The provided {@link Timeline} may be null to prevent an
* immediate source info refresh message when preparing the media source. It can be manually set
- * later using {@link #setNewSourceInfo(Timeline, Object)}.
+ * later using {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(@Nullable Timeline timeline, TrackGroupArray trackGroupArray) {
- this.timeline = timeline;
+ if (timeline != null) {
+ this.timeline = timeline;
+ }
+ this.trackGroupArray = trackGroupArray;
this.activeMediaPeriods = new ArrayList<>();
this.createdMediaPeriods = new ArrayList<>();
- this.trackGroupArray = trackGroupArray;
+ }
+
+ @Nullable
+ protected Timeline getTimeline() {
+ return timeline;
}
@Override
@Nullable
public Object getTag() {
- boolean hasTimeline = timeline != null && !timeline.isEmpty();
- return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null;
+ if (timeline == null || timeline.isEmpty()) {
+ return null;
+ }
+ return timeline.getWindow(0, new Timeline.Window()).tag;
}
- @Nullable
@Override
+ @Nullable
public Timeline getInitialTimeline() {
return timeline == null || timeline == Timeline.EMPTY || timeline.getWindowCount() == 1
? null
@@ -143,7 +153,7 @@
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
- int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
+ int periodIndex = Util.castNonNull(timeline).getIndexOfPeriod(id.periodUid);
Assertions.checkArgument(periodIndex != C.INDEX_UNSET);
Period period = timeline.getPeriod(periodIndex, new Period());
EventDispatcher eventDispatcher =
@@ -171,15 +181,15 @@
assertThat(activeMediaPeriods.isEmpty()).isTrue();
releasedSource = true;
preparedSource = false;
- sourceInfoRefreshHandler.removeCallbacksAndMessages(null);
+ Util.castNonNull(sourceInfoRefreshHandler).removeCallbacksAndMessages(null);
sourceInfoRefreshHandler = null;
}
/**
- * Sets a new timeline and manifest. If the source is already prepared, this triggers a source
- * info refresh message being sent to the listener.
+ * Sets a new timeline. If the source is already prepared, this triggers a source info refresh
+ * message being sent to the listener.
*/
- public synchronized void setNewSourceInfo(final Timeline newTimeline, final Object newManifest) {
+ public synchronized void setNewSourceInfo(final Timeline newTimeline) {
if (sourceInfoRefreshHandler != null) {
sourceInfoRefreshHandler.post(
() -> {
@@ -238,7 +248,7 @@
}
private void finishSourcePreparation() {
- refreshSourceInfo(timeline);
+ refreshSourceInfo(Assertions.checkStateNotNull(timeline));
if (!timeline.isEmpty()) {
MediaLoadData mediaLoadData =
new MediaLoadData(
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java
index 987a9e3..b79e211 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeRenderer.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.testutil;
-import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
@@ -25,18 +24,22 @@
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
-import java.util.Arrays;
+import com.google.android.exoplayer2.util.Util;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * Fake {@link Renderer} that supports any format with the matching MIME type. The renderer
- * verifies that it reads one of the given {@link Format}s.
+ * Fake {@link Renderer} that supports any format with the matching track type.
+ *
+ * <p>The renderer verifies that all the formats it reads have the provided track type.
*/
public class FakeRenderer extends BaseRenderer {
+ private static final String TAG = "FakeRenderer";
/**
* The amount of time ahead of the current playback position that the renderer reads from the
* source. A real renderer will typically read ahead by a small amount due to pipelining through
@@ -44,29 +47,34 @@
*/
private static final long SOURCE_READAHEAD_US = 250000;
- private final List<Format> expectedFormats;
private final DecoderInputBuffer buffer;
private long playbackPositionUs;
private long lastSamplePositionUs;
+ private boolean hasPendingBuffer;
+ private List<Format> formatsRead;
public boolean isEnded;
public int positionResetCount;
- public int formatReadCount;
public int sampleBufferReadCount;
- public FakeRenderer(Format... expectedFormats) {
- super(expectedFormats.length == 0 ? C.TRACK_TYPE_UNKNOWN
- : MimeTypes.getTrackType(expectedFormats[0].sampleMimeType));
- this.expectedFormats = Collections.unmodifiableList(Arrays.asList(expectedFormats));
+ public FakeRenderer(int trackType) {
+ super(trackType);
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
lastSamplePositionUs = Long.MIN_VALUE;
+ formatsRead = new ArrayList<>();
+ }
+
+ @Override
+ public String getName() {
+ return TAG;
}
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
playbackPositionUs = positionUs;
lastSamplePositionUs = Long.MIN_VALUE;
+ hasPendingBuffer = false;
positionResetCount++;
isEnded = false;
}
@@ -77,32 +85,52 @@
return;
}
playbackPositionUs = positionUs;
- while (lastSamplePositionUs < positionUs + SOURCE_READAHEAD_US) {
- FormatHolder formatHolder = getFormatHolder();
- buffer.clear();
- int result = readSource(formatHolder, buffer, false);
- if (result == C.RESULT_FORMAT_READ) {
- formatReadCount++;
- assertThat(expectedFormats).contains(formatHolder.format);
- onFormatChanged(formatHolder.format);
- } else if (result == C.RESULT_BUFFER_READ) {
- if (buffer.isEndOfStream()) {
- isEnded = true;
+ while (true) {
+ if (!hasPendingBuffer) {
+ FormatHolder formatHolder = getFormatHolder();
+ buffer.clear();
+ @SampleStream.ReadDataResult
+ int result = readSource(formatHolder, buffer, /* formatRequired= */ false);
+ if (result == C.RESULT_FORMAT_READ) {
+ Format format = Assertions.checkNotNull(formatHolder.format);
+ if (MimeTypes.getTrackType(format.sampleMimeType) != getTrackType()) {
+ throw ExoPlaybackException.createForRenderer(
+ new IllegalStateException(
+ Util.formatInvariant(
+ "Format track type (%s) doesn't match renderer track type (%s).",
+ MimeTypes.getTrackType(format.sampleMimeType), getTrackType())),
+ getName(),
+ getIndex(),
+ format,
+ FORMAT_UNSUPPORTED_TYPE);
+ }
+ formatsRead.add(format);
+ onFormatChanged(format);
+ } else if (result == C.RESULT_BUFFER_READ) {
+ if (buffer.isEndOfStream()) {
+ isEnded = true;
+ return;
+ }
+ hasPendingBuffer = true;
+ } else {
+ Assertions.checkState(result == C.RESULT_NOTHING_READ);
+ return;
+ }
+ }
+ if (hasPendingBuffer) {
+ if (!shouldProcessBuffer(buffer.timeUs, positionUs)) {
return;
}
lastSamplePositionUs = buffer.timeUs;
sampleBufferReadCount++;
- onBufferRead();
- } else {
- Assertions.checkState(result == C.RESULT_NOTHING_READ);
- return;
+ hasPendingBuffer = false;
}
}
}
@Override
public boolean isReady() {
- return lastSamplePositionUs >= playbackPositionUs || isSourceReady();
+ return lastSamplePositionUs >= playbackPositionUs || hasPendingBuffer || isSourceReady();
}
@Override
@@ -113,7 +141,8 @@
@Override
@Capabilities
public int supportsFormat(Format format) throws ExoPlaybackException {
- return getTrackType() == MimeTypes.getTrackType(format.sampleMimeType)
+ int trackType = MimeTypes.getTrackType(format.sampleMimeType);
+ return trackType != C.TRACK_TYPE_UNKNOWN && trackType == getTrackType()
? RendererCapabilities.create(FORMAT_HANDLED, ADAPTIVE_SEAMLESS, TUNNELING_NOT_SUPPORTED)
: RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
}
@@ -121,6 +150,19 @@
/** Called when the renderer reads a new format. */
protected void onFormatChanged(Format format) {}
- /** Called when the renderer read a sample from the buffer. */
- protected void onBufferRead() {}
+ /** Returns the list of formats read by the renderer. */
+ public List<Format> getFormatsRead() {
+ return Collections.unmodifiableList(formatsRead);
+ }
+
+ /**
+ * Called before the renderer processes a buffer.
+ *
+ * @param bufferTimeUs The buffer timestamp, in microseconds.
+ * @param playbackPositionUs The playback position, in microseconds
+ * @return Whether the buffer should be processed.
+ */
+ protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
+ return bufferTimeUs < playbackPositionUs + SOURCE_READAHEAD_US;
+ }
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
index 150f61d..692cf6e 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
@@ -25,14 +25,12 @@
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
/**
* Fake {@link SampleStream} that outputs a given {@link Format}, any amount of {@link
* FakeSampleStreamItem items}, then end of stream.
*/
-public final class FakeSampleStream implements SampleStream {
+public class FakeSampleStream implements SampleStream {
/** Item to customize a return value of {@link FakeSampleStream#readData}. */
public static final class FakeSampleStreamItem {
@@ -81,10 +79,11 @@
}
}
- /** List for use when a single sample is to be output, followed by the end of stream. */
- public static final List<FakeSampleStreamItem> SINGLE_SAMPLE_THEN_END_OF_STREAM =
- Arrays.asList(
- new FakeSampleStreamItem(new byte[] {0}), FakeSampleStreamItem.END_OF_STREAM_ITEM);
+ /** Constant array for use when a single sample is to be output, followed by the end of stream. */
+ public static final FakeSampleStreamItem[] SINGLE_SAMPLE_THEN_END_OF_STREAM =
+ new FakeSampleStreamItem[] {
+ new FakeSampleStreamItem(new byte[] {0}), FakeSampleStreamItem.END_OF_STREAM_ITEM
+ };
private final ArrayDeque<FakeSampleStreamItem> fakeSampleStreamItems;
private final int timeUsIncrement;
@@ -92,7 +91,7 @@
@Nullable private final EventDispatcher eventDispatcher;
private Format format;
- private int timeUs;
+ private long timeUs;
private boolean readFormat;
private boolean readEOSBuffer;
@@ -109,10 +108,11 @@
this(
format,
eventDispatcher,
+ /* firstSampleTimeUs= */ 0,
+ /* timeUsIncrement= */ 0,
shouldOutputSample
? SINGLE_SAMPLE_THEN_END_OF_STREAM
- : Collections.singletonList(FakeSampleStreamItem.END_OF_STREAM_ITEM),
- /* timeUsIncrement= */ 0);
+ : new FakeSampleStreamItem[] {FakeSampleStreamItem.END_OF_STREAM_ITEM});
}
/**
@@ -121,31 +121,34 @@
*
* @param format The {@link Format} to output.
* @param eventDispatcher An {@link EventDispatcher} to notify of read events.
- * @param fakeSampleStreamItems The list of {@link FakeSampleStreamItem items} to customize the
- * return values of {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. Note that
- * once an EOS buffer has been read, that will return every time readData is called.
+ * @param firstSampleTimeUs The time at which samples will start being output, in microseconds.
* @param timeUsIncrement The time each sample should increase by, in microseconds.
+ * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to customize the return
+ * values of {@link #readData(FormatHolder, DecoderInputBuffer, boolean)}. Note that once an
+ * EOS buffer has been read, that will return every time readData is called.
*/
public FakeSampleStream(
Format format,
@Nullable EventDispatcher eventDispatcher,
- List<FakeSampleStreamItem> fakeSampleStreamItems,
- int timeUsIncrement) {
+ long firstSampleTimeUs,
+ int timeUsIncrement,
+ FakeSampleStreamItem... fakeSampleStreamItems) {
this.format = format;
this.eventDispatcher = eventDispatcher;
- this.fakeSampleStreamItems = new ArrayDeque<>(fakeSampleStreamItems);
+ this.fakeSampleStreamItems = new ArrayDeque<>(Arrays.asList(fakeSampleStreamItems));
+ this.timeUs = firstSampleTimeUs;
this.timeUsIncrement = timeUsIncrement;
}
/**
- * Resets the samples provided by this sample stream to the provided list.
+ * Clears and assigns new samples provided by this sample stream.
*
- * @param fakeSampleStreamItems The list of {@link FakeSampleStreamItem items} to provide.
* @param timeUs The time at which samples will start being output, in microseconds.
+ * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to provide.
*/
- public void resetSampleStreamItems(List<FakeSampleStreamItem> fakeSampleStreamItems, int timeUs) {
+ public void resetSampleStreamItems(long timeUs, FakeSampleStreamItem... fakeSampleStreamItems) {
this.fakeSampleStreamItems.clear();
- this.fakeSampleStreamItems.addAll(fakeSampleStreamItems);
+ this.fakeSampleStreamItems.addAll(Arrays.asList(fakeSampleStreamItems));
this.timeUs = timeUs;
readEOSBuffer = false;
}
@@ -161,7 +164,7 @@
@Override
public boolean isReady() {
- return true;
+ return !readFormat || readEOSBuffer || !fakeSampleStreamItems.isEmpty();
}
@Override
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java
index 6b738ec..19d172d 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java
@@ -24,9 +24,7 @@
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
-/**
- * Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s.
- */
+/** Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s. */
public final class FakeTimeline extends Timeline {
/**
@@ -34,20 +32,47 @@
*/
public static final class TimelineWindowDefinition {
- /** Default test window duration in microseconds. */
+ /** Default window duration in microseconds. */
public static final long DEFAULT_WINDOW_DURATION_US = 10 * C.MICROS_PER_SECOND;
+ /** Default offset of a window in its first period in microseconds. */
+ public static final long DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US =
+ 10_000 * C.MICROS_PER_SECOND;
+
public final int periodCount;
public final Object id;
public final boolean isSeekable;
public final boolean isDynamic;
public final boolean isLive;
+ public final boolean isPlaceholder;
public final long durationUs;
+ public final long defaultPositionUs;
+ public final long windowOffsetInFirstPeriodUs;
public final AdPlaybackState adPlaybackState;
/**
- * Creates a seekable, non-dynamic window definition with a duration of
- * {@link #DEFAULT_WINDOW_DURATION_US}.
+ * Creates a window definition that corresponds to a dummy placeholder timeline using the given
+ * tag.
+ *
+ * @param tag The tag to use in the timeline.
+ */
+ public static TimelineWindowDefinition createDummy(Object tag) {
+ return new TimelineWindowDefinition(
+ /* periodCount= */ 1,
+ /* id= */ tag,
+ /* isSeekable= */ false,
+ /* isDynamic= */ true,
+ /* isLive= */ false,
+ /* isPlaceholder= */ true,
+ /* durationUs= */ C.TIME_UNSET,
+ /* defaultPositionUs= */ 0,
+ /* windowOffsetInFirstPeriodUs= */ 0,
+ AdPlaybackState.NONE);
+ }
+
+ /**
+ * Creates a seekable, non-dynamic window definition with a duration of {@link
+ * #DEFAULT_WINDOW_DURATION_US}.
*
* @param periodCount The number of periods in the window. Each period get an equal slice of the
* total window duration.
@@ -107,7 +132,10 @@
isSeekable,
isDynamic,
/* isLive= */ isDynamic,
+ /* isPlaceholder= */ false,
durationUs,
+ /* defaultPositionUs= */ 0,
+ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
adPlaybackState);
}
@@ -120,7 +148,11 @@
* @param isSeekable Whether the window is seekable.
* @param isDynamic Whether the window is dynamic.
* @param isLive Whether the window is live.
+ * @param isPlaceholder Whether the window is a placeholder.
* @param durationUs The duration of the window in microseconds.
+ * @param defaultPositionUs The default position of the window in microseconds.
+ * @param windowOffsetInFirstPeriodUs The offset of the window in the first period, in
+ * microseconds.
* @param adPlaybackState The ad playback state.
*/
public TimelineWindowDefinition(
@@ -129,17 +161,23 @@
boolean isSeekable,
boolean isDynamic,
boolean isLive,
+ boolean isPlaceholder,
long durationUs,
+ long defaultPositionUs,
+ long windowOffsetInFirstPeriodUs,
AdPlaybackState adPlaybackState) {
+ Assertions.checkArgument(durationUs != C.TIME_UNSET || periodCount == 1);
this.periodCount = periodCount;
this.id = id;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
+ this.isPlaceholder = isPlaceholder;
this.durationUs = durationUs;
+ this.defaultPositionUs = defaultPositionUs;
+ this.windowOffsetInFirstPeriodUs = windowOffsetInFirstPeriodUs;
this.adPlaybackState = adPlaybackState;
}
-
}
private static final long AD_DURATION_US = 10 * C.MICROS_PER_SECOND;
@@ -222,7 +260,7 @@
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
- return window.set(
+ window.set(
/* uid= */ windowDefinition.id,
/* tag= */ windowDefinition.id,
manifests[windowIndex],
@@ -232,11 +270,13 @@
windowDefinition.isSeekable,
windowDefinition.isDynamic,
windowDefinition.isLive,
- /* defaultPositionUs= */ 0,
+ windowDefinition.defaultPositionUs,
windowDefinition.durationUs,
periodOffsets[windowIndex],
periodOffsets[windowIndex + 1] - 1,
- /* positionInFirstPeriodUs= */ 0);
+ windowDefinition.windowOffsetInFirstPeriodUs);
+ window.isPlaceholder = windowDefinition.isPlaceholder;
+ return window;
}
@Override
@@ -251,8 +291,20 @@
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
Object id = setIds ? windowPeriodIndex : null;
Object uid = setIds ? Pair.create(windowDefinition.id, windowPeriodIndex) : null;
- long periodDurationUs = windowDefinition.durationUs / windowDefinition.periodCount;
- long positionInWindowUs = periodDurationUs * windowPeriodIndex;
+ // Arbitrarily set period duration by distributing window duration equally among all periods.
+ long periodDurationUs =
+ windowDefinition.durationUs == C.TIME_UNSET
+ ? C.TIME_UNSET
+ : windowDefinition.durationUs / windowDefinition.periodCount;
+ long positionInWindowUs;
+ if (windowPeriodIndex == 0) {
+ if (windowDefinition.durationUs != C.TIME_UNSET) {
+ periodDurationUs += windowDefinition.windowOffsetInFirstPeriodUs;
+ }
+ positionInWindowUs = -windowDefinition.windowOffsetInFirstPeriodUs;
+ } else {
+ positionInWindowUs = periodDurationUs * windowPeriodIndex;
+ }
return period.set(
id,
uid,
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
index 0389569..46b5b5f 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
@@ -17,10 +17,13 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
+import com.google.android.exoplayer2.testutil.Dumper.Dumpable;
+import com.google.android.exoplayer2.upstream.DataReader;
+import com.google.android.exoplayer2.util.Function;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
@@ -29,47 +32,42 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* A fake {@link TrackOutput}.
*/
public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable {
- private final ArrayList<Long> sampleTimesUs;
- private final ArrayList<Integer> sampleFlags;
- private final ArrayList<Integer> sampleStartOffsets;
- private final ArrayList<Integer> sampleEndOffsets;
- private final ArrayList<CryptoData> cryptoDatas;
+ private final ArrayList<DumpableSampleInfo> sampleInfos;
+ private final ArrayList<Dumpable> dumpables;
private byte[] sampleData;
- public Format format;
+ private int formatCount;
+
+ @Nullable public Format lastFormat;
public FakeTrackOutput() {
+ sampleInfos = new ArrayList<>();
+ dumpables = new ArrayList<>();
sampleData = Util.EMPTY_BYTE_ARRAY;
- sampleTimesUs = new ArrayList<>();
- sampleFlags = new ArrayList<>();
- sampleStartOffsets = new ArrayList<>();
- sampleEndOffsets = new ArrayList<>();
- cryptoDatas = new ArrayList<>();
+ formatCount = 0;
}
public void clear() {
+ sampleInfos.clear();
+ dumpables.clear();
sampleData = Util.EMPTY_BYTE_ARRAY;
- sampleTimesUs.clear();
- sampleFlags.clear();
- sampleStartOffsets.clear();
- sampleEndOffsets.clear();
- cryptoDatas.clear();
+ formatCount = 0;
}
@Override
public void format(Format format) {
- this.format = format;
+ addFormat(format);
}
@Override
- public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
- throws IOException, InterruptedException {
+ public int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException {
byte[] newData = new byte[length];
int bytesAppended = input.read(newData, 0, length);
if (bytesAppended == C.RESULT_END_OF_INPUT) {
@@ -91,121 +89,245 @@
}
@Override
- public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
- CryptoData cryptoData) {
- if (format == null) {
+ public void sampleMetadata(
+ long timeUs,
+ @C.BufferFlags int flags,
+ int size,
+ int offset,
+ @Nullable CryptoData cryptoData) {
+ if (lastFormat == null) {
throw new IllegalStateException("TrackOutput must receive format before sampleMetadata");
}
- if (format.maxInputSize != Format.NO_VALUE && size > format.maxInputSize) {
+ if (lastFormat.maxInputSize != Format.NO_VALUE && size > lastFormat.maxInputSize) {
throw new IllegalStateException("Sample size exceeds Format.maxInputSize");
}
- sampleTimesUs.add(timeUs);
- sampleFlags.add(flags);
- sampleStartOffsets.add(sampleData.length - offset - size);
- sampleEndOffsets.add(sampleData.length - offset);
- cryptoDatas.add(cryptoData);
+ if (dumpables.isEmpty()) {
+ addFormat(lastFormat);
+ }
+ addSampleInfo(
+ timeUs, flags, sampleData.length - offset - size, sampleData.length - offset, cryptoData);
}
public void assertSampleCount(int count) {
- assertThat(sampleTimesUs).hasSize(count);
+ assertThat(sampleInfos).hasSize(count);
}
- public void assertSample(int index, byte[] data, long timeUs, int flags, CryptoData cryptoData) {
+ public void assertSample(
+ int index, byte[] data, long timeUs, int flags, @Nullable CryptoData cryptoData) {
byte[] actualData = getSampleData(index);
assertThat(actualData).isEqualTo(data);
- assertThat(sampleTimesUs.get(index)).isEqualTo(timeUs);
- assertThat(sampleFlags.get(index)).isEqualTo(flags);
- assertThat(cryptoDatas.get(index)).isEqualTo(cryptoData);
+ assertThat(getSampleTimeUs(index)).isEqualTo(timeUs);
+ assertThat(getSampleFlags(index)).isEqualTo(flags);
+ assertThat(getSampleCryptoData(index)).isEqualTo(cryptoData);
}
public byte[] getSampleData(int index) {
- return Arrays.copyOfRange(sampleData, sampleStartOffsets.get(index),
- sampleEndOffsets.get(index));
+ return Arrays.copyOfRange(sampleData, getSampleStartOffset(index), getSampleEndOffset(index));
+ }
+
+ private byte[] getSampleData(int fromIndex, int toIndex) {
+ return Arrays.copyOfRange(sampleData, fromIndex, toIndex);
}
public long getSampleTimeUs(int index) {
- return sampleTimesUs.get(index);
+ return sampleInfos.get(index).timeUs;
}
public int getSampleFlags(int index) {
- return sampleFlags.get(index);
+ return sampleInfos.get(index).flags;
}
+ @Nullable
public CryptoData getSampleCryptoData(int index) {
- return cryptoDatas.get(index);
+ return sampleInfos.get(index).cryptoData;
}
public int getSampleCount() {
- return sampleTimesUs.size();
+ return sampleInfos.size();
}
public List<Long> getSampleTimesUs() {
- return Collections.unmodifiableList(sampleTimesUs);
- }
-
- public void assertEquals(FakeTrackOutput expected) {
- assertThat(format).isEqualTo(expected.format);
- assertThat(sampleTimesUs).hasSize(expected.sampleTimesUs.size());
- assertThat(sampleData).isEqualTo(expected.sampleData);
- for (int i = 0; i < sampleTimesUs.size(); i++) {
- assertThat(sampleTimesUs.get(i)).isEqualTo(expected.sampleTimesUs.get(i));
- assertThat(sampleFlags.get(i)).isEqualTo(expected.sampleFlags.get(i));
- assertThat(sampleStartOffsets.get(i)).isEqualTo(expected.sampleStartOffsets.get(i));
- assertThat(sampleEndOffsets.get(i)).isEqualTo(expected.sampleEndOffsets.get(i));
- if (expected.cryptoDatas.get(i) == null) {
- assertThat(cryptoDatas.get(i)).isNull();
- } else {
- assertThat(cryptoDatas.get(i)).isEqualTo(expected.cryptoDatas.get(i));
- }
+ List<Long> sampleTimesUs = new ArrayList<>();
+ for (DumpableSampleInfo sampleInfo : sampleInfos) {
+ sampleTimesUs.add(sampleInfo.timeUs);
}
+ return Collections.unmodifiableList(sampleTimesUs);
}
@Override
public void dump(Dumper dumper) {
- dumper
- .startBlock("format")
- .add("bitrate", format.bitrate)
- .add("id", format.id)
- .add("containerMimeType", format.containerMimeType)
- .add("sampleMimeType", format.sampleMimeType)
- .add("maxInputSize", format.maxInputSize)
- .add("width", format.width)
- .add("height", format.height)
- .add("frameRate", format.frameRate)
- .add("rotationDegrees", format.rotationDegrees)
- .add("pixelWidthHeightRatio", format.pixelWidthHeightRatio)
- .add("channelCount", format.channelCount)
- .add("sampleRate", format.sampleRate)
- .add("pcmEncoding", format.pcmEncoding)
- .add("encoderDelay", format.encoderDelay)
- .add("encoderPadding", format.encoderPadding)
- .add("subsampleOffsetUs", format.subsampleOffsetUs)
- .add("selectionFlags", format.selectionFlags)
- .add("language", format.language)
- .add("drmInitData", format.drmInitData != null ? format.drmInitData.hashCode() : "-")
- .add("metadata", format.metadata);
-
- dumper.startBlock("initializationData");
- for (int i = 0; i < format.initializationData.size(); i++) {
- dumper.add("data", format.initializationData.get(i));
- }
- dumper.endBlock().endBlock();
-
dumper.add("total output bytes", sampleData.length);
- dumper.add("sample count", sampleTimesUs.size());
+ dumper.add("sample count", sampleInfos.size());
+ if (dumpables.isEmpty() && lastFormat != null) {
+ new DumpableFormat(lastFormat, 0).dump(dumper);
+ }
+ for (int i = 0; i < dumpables.size(); i++) {
+ dumpables.get(i).dump(dumper);
+ }
+ }
- for (int i = 0; i < sampleTimesUs.size(); i++) {
- dumper.startBlock("sample " + i)
- .add("time", sampleTimesUs.get(i))
- .add("flags", sampleFlags.get(i))
- .add("data", getSampleData(i));
- CryptoData cryptoData = cryptoDatas.get(i);
+ private int getSampleStartOffset(int index) {
+ return sampleInfos.get(index).startOffset;
+ }
+
+ private int getSampleEndOffset(int index) {
+ return sampleInfos.get(index).endOffset;
+ }
+
+ private void addFormat(Format format) {
+ lastFormat = format;
+ dumpables.add(new DumpableFormat(format, formatCount));
+ formatCount++;
+ }
+
+ private void addSampleInfo(
+ long timeUs, int flags, int startOffset, int endOffset, @Nullable CryptoData cryptoData) {
+ DumpableSampleInfo sampleInfo =
+ new DumpableSampleInfo(timeUs, flags, startOffset, endOffset, cryptoData, getSampleCount());
+ sampleInfos.add(sampleInfo);
+ dumpables.add(sampleInfo);
+ }
+
+ private final class DumpableSampleInfo implements Dumper.Dumpable {
+ public final long timeUs;
+ public final int flags;
+ public final int startOffset;
+ public final int endOffset;
+ @Nullable public final CryptoData cryptoData;
+ public final int index;
+
+ public DumpableSampleInfo(
+ long timeUs,
+ int flags,
+ int startOffset,
+ int endOffset,
+ @Nullable CryptoData cryptoData,
+ int index) {
+ this.timeUs = timeUs;
+ this.flags = flags;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.cryptoData = cryptoData;
+ this.index = index;
+ }
+
+ @Override
+ public void dump(Dumper dumper) {
+ dumper
+ .startBlock("sample " + index)
+ .add("time", timeUs)
+ .add("flags", flags)
+ .add("data", getSampleData(startOffset, endOffset));
if (cryptoData != null) {
dumper.add("crypto mode", cryptoData.cryptoMode);
dumper.add("encryption key", cryptoData.encryptionKey);
}
dumper.endBlock();
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DumpableSampleInfo that = (DumpableSampleInfo) o;
+ return timeUs == that.timeUs
+ && flags == that.flags
+ && startOffset == that.startOffset
+ && endOffset == that.endOffset
+ && index == that.index
+ && Util.areEqual(cryptoData, that.cryptoData);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) timeUs;
+ result = 31 * result + flags;
+ result = 31 * result + startOffset;
+ result = 31 * result + endOffset;
+ result = 31 * result + (cryptoData == null ? 0 : cryptoData.hashCode());
+ result = 31 * result + index;
+ return result;
+ }
}
+ private static final class DumpableFormat implements Dumper.Dumpable {
+ private final Format format;
+ public final int index;
+
+ private static final Format DEFAULT_FORMAT = new Format.Builder().build();
+
+ public DumpableFormat(Format format, int index) {
+ this.format = format;
+ this.index = index;
+ }
+
+ @Override
+ public void dump(Dumper dumper) {
+ dumper.startBlock("format " + index);
+ addIfNonDefault(dumper, "averageBitrate", format -> format.averageBitrate);
+ addIfNonDefault(dumper, "peakBitrate", format -> format.peakBitrate);
+ addIfNonDefault(dumper, "id", format -> format.id);
+ addIfNonDefault(dumper, "containerMimeType", format -> format.containerMimeType);
+ addIfNonDefault(dumper, "sampleMimeType", format -> format.sampleMimeType);
+ addIfNonDefault(dumper, "codecs", format -> format.codecs);
+ addIfNonDefault(dumper, "maxInputSize", format -> format.maxInputSize);
+ addIfNonDefault(dumper, "width", format -> format.width);
+ addIfNonDefault(dumper, "height", format -> format.height);
+ addIfNonDefault(dumper, "frameRate", format -> format.frameRate);
+ addIfNonDefault(dumper, "rotationDegrees", format -> format.rotationDegrees);
+ addIfNonDefault(dumper, "pixelWidthHeightRatio", format -> format.pixelWidthHeightRatio);
+ addIfNonDefault(dumper, "channelCount", format -> format.channelCount);
+ addIfNonDefault(dumper, "sampleRate", format -> format.sampleRate);
+ addIfNonDefault(dumper, "pcmEncoding", format -> format.pcmEncoding);
+ addIfNonDefault(dumper, "encoderDelay", format -> format.encoderDelay);
+ addIfNonDefault(dumper, "encoderPadding", format -> format.encoderPadding);
+ addIfNonDefault(dumper, "subsampleOffsetUs", format -> format.subsampleOffsetUs);
+ addIfNonDefault(dumper, "selectionFlags", format -> format.selectionFlags);
+ addIfNonDefault(dumper, "language", format -> format.language);
+ if (format.drmInitData != null) {
+ dumper.add("drmInitData", format.drmInitData.hashCode());
+ }
+ addIfNonDefault(dumper, "metadata", format -> format.metadata);
+ if (!format.initializationData.isEmpty()) {
+ dumper.startBlock("initializationData");
+ for (int i = 0; i < format.initializationData.size(); i++) {
+ dumper.add("data", format.initializationData.get(i));
+ }
+ dumper.endBlock();
+ }
+ dumper.endBlock();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DumpableFormat that = (DumpableFormat) o;
+ return index == that.index && format.equals(that.format);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = format.hashCode();
+ result = 31 * result + index;
+ return result;
+ }
+
+ private void addIfNonDefault(
+ Dumper dumper, String field, Function<Format, @NullableType Object> getFieldFunction) {
+ @Nullable Object thisValue = getFieldFunction.apply(format);
+ @Nullable Object defaultValue = getFieldFunction.apply(DEFAULT_FORMAT);
+ if (!Util.areEqual(thisValue, defaultValue)) {
+ dumper.add(field, thisValue);
+ }
+ }
+ }
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java
index b479ebe..5e03405 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
@@ -109,6 +110,7 @@
}
@Override
+ @Nullable
public Object getSelectionData() {
return null;
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java
index ac39ba8..3b44e62 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java
@@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.testutil;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.exoplayer2.RendererCapabilities.AdaptiveSupport;
+import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@@ -23,6 +26,7 @@
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import java.util.ArrayList;
import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */
public class FakeTrackSelector extends DefaultTrackSelector {
@@ -30,7 +34,7 @@
private final FakeTrackSelectionFactory fakeTrackSelectionFactory;
public FakeTrackSelector() {
- this(false);
+ this(/* mayReuseTrackSelection= */ false);
}
/**
@@ -43,18 +47,19 @@
}
private FakeTrackSelector(FakeTrackSelectionFactory fakeTrackSelectionFactory) {
- super(fakeTrackSelectionFactory);
+ super(ApplicationProvider.getApplicationContext(), fakeTrackSelectionFactory);
this.fakeTrackSelectionFactory = fakeTrackSelectionFactory;
}
@Override
- protected TrackSelection.Definition[] selectAllTracks(
+ protected TrackSelection.@NullableType Definition[] selectAllTracks(
MappedTrackInfo mappedTrackInfo,
- int[][][] rendererFormatSupports,
- int[] rendererMixedMimeTypeAdaptationSupports,
+ @Capabilities int[][][] rendererFormatSupports,
+ @AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports,
Parameters params) {
int rendererCount = mappedTrackInfo.getRendererCount();
- TrackSelection.Definition[] definitions = new TrackSelection.Definition[rendererCount];
+ TrackSelection.@NullableType Definition[] definitions =
+ new TrackSelection.Definition[rendererCount];
for (int i = 0; i < rendererCount; i++) {
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
boolean hasTracks = trackGroupArray.length > 0;
@@ -80,7 +85,7 @@
@Override
public TrackSelection[] createTrackSelections(
- TrackSelection.Definition[] definitions, BandwidthMeter bandwidthMeter) {
+ TrackSelection.@NullableType Definition[] definitions, BandwidthMeter bandwidthMeter) {
TrackSelection[] selections = new TrackSelection[definitions.length];
for (int i = 0; i < definitions.length; i++) {
TrackSelection.Definition definition = definitions[i];
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
new file mode 100644
index 0000000..2016f9d
--- /dev/null
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.testutil;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.Renderer;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.video.VideoRendererEventListener;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */
+public class FakeVideoRenderer extends FakeRenderer {
+
+ private final VideoRendererEventListener.EventDispatcher eventDispatcher;
+ private final DecoderCounters decoderCounters;
+ private @MonotonicNonNull Format format;
+ private long streamOffsetUs;
+ private boolean renderedFirstFrameAfterReset;
+ private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
+ private boolean renderedFirstFrameAfterEnable;
+
+ public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) {
+ super(C.TRACK_TYPE_VIDEO);
+ eventDispatcher = new VideoRendererEventListener.EventDispatcher(handler, eventListener);
+ decoderCounters = new DecoderCounters();
+ }
+
+ @Override
+ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
+ throws ExoPlaybackException {
+ super.onEnabled(joining, mayRenderStartOfStream);
+ eventDispatcher.enabled(decoderCounters);
+ mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
+ renderedFirstFrameAfterEnable = false;
+ }
+
+ @Override
+ protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
+ super.onStreamChanged(formats, offsetUs);
+ streamOffsetUs = offsetUs;
+ if (renderedFirstFrameAfterReset) {
+ renderedFirstFrameAfterReset = false;
+ }
+ }
+
+ @Override
+ protected void onStopped() throws ExoPlaybackException {
+ super.onStopped();
+ eventDispatcher.droppedFrames(/* droppedFrameCount= */ 0, /* elapsedMs= */ 0);
+ eventDispatcher.reportVideoFrameProcessingOffset(
+ /* totalProcessingOffsetUs= */ 400000,
+ /* frameCount= */ 10,
+ Assertions.checkNotNull(format));
+ }
+
+ @Override
+ protected void onDisabled() {
+ super.onDisabled();
+ eventDispatcher.disabled(decoderCounters);
+ }
+
+ @Override
+ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
+ super.onPositionReset(positionUs, joining);
+ renderedFirstFrameAfterReset = false;
+ }
+
+ @Override
+ protected void onFormatChanged(Format format) {
+ eventDispatcher.inputFormatChanged(format);
+ eventDispatcher.decoderInitialized(
+ /* decoderName= */ "fake.video.decoder",
+ /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
+ /* initializationDurationMs= */ 0);
+ this.format = format;
+ }
+
+ @Override
+ protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
+ boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
+ boolean shouldRenderFirstFrame =
+ !renderedFirstFrameAfterEnable
+ ? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted)
+ : !renderedFirstFrameAfterReset;
+ shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs;
+ if (shouldProcess && !renderedFirstFrameAfterReset) {
+ @MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
+ eventDispatcher.videoSizeChanged(
+ format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
+ eventDispatcher.renderedFirstFrame(/* surface= */ null);
+ renderedFirstFrameAfterReset = true;
+ renderedFirstFrameAfterEnable = true;
+ }
+ return shouldProcess;
+ }
+}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java
index 39429a8..49c3310 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/HostActivity.java
@@ -29,13 +29,14 @@
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
+import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-/**
- * A host activity for performing playback tests.
- */
+/** A host activity for performing playback tests. */
public final class HostActivity extends Activity implements SurfaceHolder.Callback {
/**
@@ -45,14 +46,15 @@
/**
* Called on the main thread when the test is started.
- * <p>
- * The test will not be started until the {@link HostActivity} has been resumed and its
+ *
+ * <p>The test will not be started until the {@link HostActivity} has been resumed and its
* {@link Surface} has been created.
*
* @param host The {@link HostActivity} in which the test is being run.
* @param surface The {@link Surface}.
+ * @param overlayFrameLayout A {@link FrameLayout} that is on top of the surface.
*/
- void onStart(HostActivity host, Surface surface);
+ void onStart(HostActivity host, Surface surface, FrameLayout overlayFrameLayout);
/**
* Called on the main thread to block until the test has stopped or {@link #forceStop()} is
@@ -83,13 +85,14 @@
private static final String LOCK_TAG = "ExoPlayerTestUtil:" + TAG;
private static final long START_TIMEOUT_MS = 5000;
- private WakeLock wakeLock;
- private WifiLock wifiLock;
- private SurfaceView surfaceView;
+ @Nullable private WakeLock wakeLock;
+ @Nullable private WifiLock wifiLock;
+ private @MonotonicNonNull SurfaceView surfaceView;
+ private @MonotonicNonNull FrameLayout overlayFrameLayout;
- private HostedTest hostedTest;
+ @Nullable private HostedTest hostedTest;
private boolean hostedTestStarted;
- private ConditionVariable hostedTestStartedCondition;
+ private @MonotonicNonNull ConditionVariable hostedTestStartedCondition;
private boolean forcedStopped;
/**
@@ -163,7 +166,7 @@
// Activity lifecycle
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(
@@ -171,15 +174,21 @@
surfaceView = findViewById(
getResources().getIdentifier("surface_view", "id", getPackageName()));
surfaceView.getHolder().addCallback(this);
+ overlayFrameLayout =
+ findViewById(getResources().getIdentifier("overlay_frame_layout", "id", getPackageName()));
}
@Override
public void onStart() {
Context appContext = getApplicationContext();
- WifiManager wifiManager = (WifiManager) appContext.getSystemService(Context.WIFI_SERVICE);
+ WifiManager wifiManager =
+ Assertions.checkStateNotNull(
+ (WifiManager) appContext.getSystemService(Context.WIFI_SERVICE));
wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOCK_TAG);
wifiLock.acquire();
- PowerManager powerManager = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
+ PowerManager powerManager =
+ Assertions.checkStateNotNull(
+ (PowerManager) appContext.getSystemService(Context.POWER_SERVICE));
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOCK_TAG);
wakeLock.acquire();
super.onStart();
@@ -199,10 +208,14 @@
if (Util.SDK_INT > 23) {
maybeStopHostedTest();
}
- wakeLock.release();
- wakeLock = null;
- wifiLock.release();
- wifiLock = null;
+ if (wakeLock != null) {
+ wakeLock.release();
+ wakeLock = null;
+ }
+ if (wifiLock != null) {
+ wifiLock.release();
+ wifiLock = null;
+ }
}
// SurfaceHolder.Callback
@@ -228,12 +241,13 @@
if (hostedTest == null || hostedTestStarted) {
return;
}
- Surface surface = surfaceView.getHolder().getSurface();
+ @Nullable Surface surface = Util.castNonNull(surfaceView).getHolder().getSurface();
if (surface != null && surface.isValid()) {
hostedTestStarted = true;
Log.d(TAG, "Starting test.");
- hostedTest.onStart(this, surface);
- hostedTestStartedCondition.open();
+ Util.castNonNull(hostedTest)
+ .onStart(this, surface, Assertions.checkNotNull(overlayFrameLayout));
+ Util.castNonNull(hostedTestStartedCondition).open();
}
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaPeriodAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaPeriodAsserts.java
index 42fc40e..27d6b08 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaPeriodAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaPeriodAsserts.java
@@ -187,7 +187,7 @@
}
private static TrackGroupArray getTrackGroups(MediaPeriod mediaPeriod) {
- AtomicReference<TrackGroupArray> trackGroupArray = new AtomicReference<>(null);
+ AtomicReference<TrackGroupArray> trackGroupArray = new AtomicReference<>();
DummyMainThread dummyMainThread = new DummyMainThread();
ConditionVariable preparedCondition = new ConditionVariable();
dummyMainThread.runOnMainThread(
@@ -231,8 +231,8 @@
return C.SELECTION_REASON_UNKNOWN;
}
- @Nullable
@Override
+ @Nullable
public Object getSelectionData() {
return null;
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
index 6518465..610995b 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
@@ -71,7 +71,7 @@
public MediaSourceTestRunner(MediaSource mediaSource, Allocator allocator) {
this.mediaSource = mediaSource;
this.allocator = allocator;
- playbackThread = new HandlerThread("PlaybackThread");
+ playbackThread = new HandlerThread("TestHandler");
playbackThread.start();
Looper playbackLooper = playbackThread.getLooper();
playbackHandler = new Handler(playbackLooper);
@@ -215,7 +215,7 @@
* runner was created if neither method has been called).
*/
public void assertNoTimelineChange() {
- assertThat(timelines.isEmpty()).isTrue();
+ assertThat(timelines).isEmpty();
}
/**
@@ -304,8 +304,10 @@
List<Integer> expectedWindowIndices = new ArrayList<>(Arrays.asList(windowIndices));
for (Pair<Integer, MediaPeriodId> windowIndexAndMediaPeriodId : completedLoads) {
if (windowIndexAndMediaPeriodId.second == null) {
- boolean loadExpected = expectedWindowIndices.remove(windowIndexAndMediaPeriodId.first);
- assertThat(loadExpected).isTrue();
+ assertWithMessage("Missing expected load")
+ .that(expectedWindowIndices)
+ .contains(windowIndexAndMediaPeriodId.first);
+ expectedWindowIndices.remove(windowIndexAndMediaPeriodId.first);
}
}
assertWithMessage("Not all expected media source loads have been completed.")
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
index 62666ea..5337685 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
@@ -16,9 +16,11 @@
package com.google.android.exoplayer2.testutil;
import android.os.Looper;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BasePlayer;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlayerMessage;
@@ -57,6 +59,11 @@
}
@Override
+ public DeviceComponent getDeviceComponent() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Looper getPlaybackLooper() {
throw new UnsupportedOperationException();
}
@@ -88,11 +95,17 @@
throw new UnsupportedOperationException();
}
+ @Deprecated
@Override
public ExoPlaybackException getPlaybackError() {
throw new UnsupportedOperationException();
}
+ @Override
+ public ExoPlaybackException getPlayerError() {
+ throw new UnsupportedOperationException();
+ }
+
/** @deprecated Use {@link #prepare()} instead. */
@Deprecated
@Override
@@ -129,6 +142,37 @@
}
@Override
+ public void setMediaItem(MediaItem mediaItem) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMediaItems(List<MediaItem> mediaItems) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMediaItems(
+ List<MediaItem> mediaItems, int startWindowIndex, long startPositionMs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void setMediaSource(MediaSource mediaSource) {
throw new UnsupportedOperationException();
}
@@ -160,6 +204,26 @@
}
@Override
+ public void addMediaItem(MediaItem mediaItem) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addMediaItem(int index, MediaItem mediaItem) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addMediaItems(List<MediaItem> mediaItems) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addMediaItems(int index, List<MediaItem> mediaItems) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void addMediaSource(MediaSource mediaSource) {
throw new UnsupportedOperationException();
}
@@ -249,18 +313,34 @@
throw new UnsupportedOperationException();
}
+ /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
- public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
throw new UnsupportedOperationException();
}
+ /** @deprecated Use {@link #getPlaybackSpeed()} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
throw new UnsupportedOperationException();
}
@Override
- public void setSeekParameters(SeekParameters seekParameters) {
+ public void setPlaybackSpeed(float playbackSpeed) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public float getPlaybackSpeed() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSeekParameters(@Nullable SeekParameters seekParameters) {
throw new UnsupportedOperationException();
}
@@ -368,4 +448,14 @@
public void setForegroundMode(boolean foregroundMode) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getPauseAtEndOfMediaItems() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java
index c42071d..48e8542 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java
@@ -18,13 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import androidx.annotation.Nullable;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.exoplayer2.offline.Download.State;
import com.google.android.exoplayer2.offline.DownloadManager;
+import com.google.android.exoplayer2.util.Util;
import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadManager.Listener} for testing. */
public final class TestDownloadManagerListener implements DownloadManager.Listener {
@@ -39,7 +42,7 @@
private final CountDownLatch initializedCondition;
private final int timeoutMs;
- private CountDownLatch downloadFinishedCondition;
+ private @MonotonicNonNull CountDownLatch downloadFinishedCondition;
@Download.FailureReason private int failureReason;
public TestDownloadManagerListener(
@@ -57,6 +60,7 @@
downloadManager.addListener(this);
}
+ @Nullable
public Integer pollStateChange(String taskId, long timeoutMs) throws InterruptedException {
return getStateQueue(taskId).poll(timeoutMs, TimeUnit.MILLISECONDS);
}
@@ -112,7 +116,7 @@
dummyMainThread.runOnMainThread(
() -> {
if (downloadManager.isIdle()) {
- downloadFinishedCondition.countDown();
+ Util.castNonNull(downloadFinishedCondition).countDown();
}
});
assertThat(downloadFinishedCondition.await(timeoutMs, TimeUnit.MILLISECONDS)).isTrue();
@@ -120,10 +124,12 @@
private ArrayBlockingQueue<Integer> getStateQueue(String taskId) {
synchronized (downloadStates) {
- if (!downloadStates.containsKey(taskId)) {
- downloadStates.put(taskId, new ArrayBlockingQueue<>(10));
+ @Nullable ArrayBlockingQueue<Integer> stateQueue = downloadStates.get(taskId);
+ if (stateQueue == null) {
+ stateQueue = new ArrayBlockingQueue<>(10);
+ downloadStates.put(taskId, stateQueue);
}
- return downloadStates.get(taskId);
+ return stateQueue;
}
}
@@ -137,11 +143,11 @@
private void assertStateInternal(String taskId, int expectedState, int timeoutMs) {
while (true) {
- Integer state = null;
+ @Nullable Integer state = null;
try {
state = pollStateChange(taskId, timeoutMs);
} catch (InterruptedException e) {
- fail(e.getMessage());
+ fail("Interrupted: " + e.getMessage());
}
if (state != null) {
if (expectedState == state) {
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
new file mode 100644
index 0000000..d0dc1df
--- /dev/null
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.testutil;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Looper;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.DefaultLoadControl;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.LoadControl;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Renderer;
+import com.google.android.exoplayer2.RenderersFactory;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.analytics.AnalyticsCollector;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.upstream.BandwidthMeter;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Clock;
+import com.google.android.exoplayer2.util.Supplier;
+import com.google.android.exoplayer2.video.VideoListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Utilities to write unit/integration tests with a SimpleExoPlayer instance that uses fake
+ * components.
+ */
+public class TestExoPlayer {
+
+ /** Reflectively call Robolectric ShadowLooper#runOneTask. */
+ private static final Object shadowLooper;
+
+ private static final Method runOneTaskMethod;
+
+ static {
+ try {
+ Class<?> clazz = Class.forName("org.robolectric.Shadows");
+ Method shadowOfMethod =
+ Assertions.checkNotNull(clazz.getDeclaredMethod("shadowOf", Looper.class));
+ shadowLooper =
+ Assertions.checkNotNull(shadowOfMethod.invoke(new Object(), Looper.getMainLooper()));
+ runOneTaskMethod = shadowLooper.getClass().getDeclaredMethod("runOneTask");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** A builder of {@link SimpleExoPlayer} instances for testing. */
+ public static class Builder {
+
+ private final Context context;
+ private Clock clock;
+ private DefaultTrackSelector trackSelector;
+ private LoadControl loadControl;
+ private BandwidthMeter bandwidthMeter;
+ @Nullable private Renderer[] renderers;
+ @Nullable private RenderersFactory renderersFactory;
+ private boolean useLazyPreparation;
+ private Looper looper;
+
+ public Builder(Context context) {
+ this.context = context;
+ clock = new AutoAdvancingFakeClock();
+ trackSelector = new DefaultTrackSelector(context);
+ loadControl = new DefaultLoadControl();
+ bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
+ looper = Assertions.checkNotNull(Looper.myLooper());
+ }
+
+ /**
+ * Sets whether to use lazy preparation.
+ *
+ * @param useLazyPreparation Whether to use lazy preparation.
+ * @return This builder.
+ */
+ public Builder setUseLazyPreparation(boolean useLazyPreparation) {
+ this.useLazyPreparation = useLazyPreparation;
+ return this;
+ }
+
+ /** Returns whether the player will use lazy preparation. */
+ public boolean getUseLazyPreparation() {
+ return useLazyPreparation;
+ }
+
+ /**
+ * Sets a {@link DefaultTrackSelector}. The default value is a {@link DefaultTrackSelector} in
+ * its initial configuration.
+ *
+ * @param trackSelector The {@link DefaultTrackSelector} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setTrackSelector(DefaultTrackSelector trackSelector) {
+ Assertions.checkNotNull(trackSelector);
+ this.trackSelector = trackSelector;
+ return this;
+ }
+
+ /** Returns the track selector used by the player. */
+ public DefaultTrackSelector getTrackSelector() {
+ return trackSelector;
+ }
+
+ /**
+ * Sets a {@link LoadControl} to be used by the player. The default value is a {@link
+ * DefaultLoadControl}.
+ *
+ * @param loadControl The {@link LoadControl} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setLoadControl(LoadControl loadControl) {
+ this.loadControl = loadControl;
+ return this;
+ }
+
+ /** Returns the {@link LoadControl} that will be used by the player. */
+ public LoadControl getLoadControl() {
+ return loadControl;
+ }
+
+ /**
+ * Sets the {@link BandwidthMeter}. The default value is a {@link DefaultBandwidthMeter} in its
+ * default configuration.
+ *
+ * @param bandwidthMeter The {@link BandwidthMeter} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
+ Assertions.checkNotNull(bandwidthMeter);
+ this.bandwidthMeter = bandwidthMeter;
+ return this;
+ }
+
+ /** Returns the bandwidth meter used by the player. */
+ public BandwidthMeter getBandwidthMeter() {
+ return bandwidthMeter;
+ }
+
+ /**
+ * Sets the {@link Renderer}s. If not set, the player will use a {@link FakeVideoRenderer} and a
+ * {@link FakeAudioRenderer}. Setting the renderers is not allowed after a call to {@link
+ * #setRenderersFactory(RenderersFactory)}.
+ *
+ * @param renderers A list of {@link Renderer}s to be used by the player.
+ * @return This builder.
+ */
+ public Builder setRenderers(Renderer... renderers) {
+ assertThat(renderersFactory).isNull();
+ this.renderers = renderers;
+ return this;
+ }
+
+ /**
+ * Returns the {@link Renderer Renderers} that have been set with {@link #setRenderers} or null
+ * if no {@link Renderer Renderers} have been explicitly set. Note that these renderers may not
+ * be the ones used by the built player, for example if a {@link #setRenderersFactory Renderer
+ * factory} has been set.
+ */
+ @Nullable
+ public Renderer[] getRenderers() {
+ return renderers;
+ }
+
+ /**
+ * Sets the {@link RenderersFactory}. The default factory creates all renderers set by {@link
+ * #setRenderers(Renderer...)}. Setting the renderer factory is not allowed after a call to
+ * {@link #setRenderers(Renderer...)}.
+ *
+ * @param renderersFactory A {@link RenderersFactory} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setRenderersFactory(RenderersFactory renderersFactory) {
+ assertThat(renderers).isNull();
+ this.renderersFactory = renderersFactory;
+ return this;
+ }
+
+ /**
+ * Returns the {@link RenderersFactory} that has been set with {@link #setRenderersFactory} or
+ * null if no factory has been explicitly set.
+ */
+ @Nullable
+ public RenderersFactory getRenderersFactory() {
+ return renderersFactory;
+ }
+
+ /**
+ * Sets the {@link Clock} to be used by the player. The default value is a {@link
+ * AutoAdvancingFakeClock}.
+ *
+ * @param clock A {@link Clock} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setClock(Clock clock) {
+ assertThat(clock).isNotNull();
+ this.clock = clock;
+ return this;
+ }
+
+ /** Returns the clock used by the player. */
+ public Clock getClock() {
+ return clock;
+ }
+
+ /**
+ * Sets the {@link Looper} to be used by the player.
+ *
+ * @param looper The {@link Looper} to be used by the player.
+ * @return This builder.
+ */
+ public Builder setLooper(Looper looper) {
+ this.looper = looper;
+ return this;
+ }
+
+ /** Returns the {@link Looper} that will be used by the player. */
+ public Looper getLooper() {
+ return looper;
+ }
+
+ /**
+ * Builds an {@link SimpleExoPlayer} using the provided values or their defaults.
+ *
+ * @return The built {@link ExoPlayerTestRunner}.
+ */
+ public SimpleExoPlayer build() {
+ // Do not update renderersFactory and renderers here, otherwise their getters may
+ // return different values before and after build() is called, making them confusing.
+ RenderersFactory playerRenderersFactory = renderersFactory;
+ if (playerRenderersFactory == null) {
+ playerRenderersFactory =
+ (eventHandler,
+ videoRendererEventListener,
+ audioRendererEventListener,
+ textRendererOutput,
+ metadataRendererOutput) ->
+ renderers != null
+ ? renderers
+ : new Renderer[] {
+ new FakeVideoRenderer(eventHandler, videoRendererEventListener),
+ new FakeAudioRenderer(eventHandler, audioRendererEventListener)
+ };
+ }
+
+ return new SimpleExoPlayer.Builder(context, playerRenderersFactory)
+ .setTrackSelector(trackSelector)
+ .setLoadControl(loadControl)
+ .setBandwidthMeter(bandwidthMeter)
+ .setAnalyticsCollector(new AnalyticsCollector(clock))
+ .setClock(clock)
+ .setUseLazyPreparation(useLazyPreparation)
+ .setLooper(looper)
+ .build();
+ }
+ }
+
+ private TestExoPlayer() {}
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player}'s state reaches the {@code
+ * expectedState}.
+ */
+ public static void runUntilPlaybackState(
+ SimpleExoPlayer player, @Player.State int expectedState) {
+ verifyMainTestThread(player);
+ if (player.getPlaybackState() == expectedState) {
+ return;
+ }
+
+ AtomicBoolean receivedExpectedState = new AtomicBoolean(false);
+ Player.EventListener listener =
+ new Player.EventListener() {
+ @Override
+ public void onPlaybackStateChanged(int state) {
+ if (state == expectedState) {
+ receivedExpectedState.set(true);
+ }
+ }
+ };
+ player.addListener(listener);
+ runUntil(() -> receivedExpectedState.get());
+ player.removeListener(listener);
+ }
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player} calls the {@link
+ * Player.EventListener#onPlaybackSpeedChanged} callback with that matches {@code
+ * expectedPlayWhenReady}.
+ */
+ public static void runUntilPlayWhenReady(SimpleExoPlayer player, boolean expectedPlayWhenReady) {
+ verifyMainTestThread(player);
+ if (player.getPlayWhenReady() == expectedPlayWhenReady) {
+ return;
+ }
+
+ AtomicBoolean receivedExpectedPlayWhenReady = new AtomicBoolean(false);
+ Player.EventListener listener =
+ new Player.EventListener() {
+ @Override
+ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
+ if (playWhenReady == expectedPlayWhenReady) {
+ receivedExpectedPlayWhenReady.set(true);
+ }
+ player.removeListener(this);
+ }
+ };
+ player.addListener(listener);
+ runUntil(() -> receivedExpectedPlayWhenReady.get());
+ }
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player} calls the {@link
+ * Player.EventListener#onTimelineChanged} callback.
+ *
+ * @param player The {@link SimpleExoPlayer}.
+ * @param expectedTimeline A specific {@link Timeline} to wait for, or null if any timeline is
+ * accepted.
+ * @return The received {@link Timeline}.
+ */
+ public static Timeline runUntilTimelineChanged(
+ SimpleExoPlayer player, @Nullable Timeline expectedTimeline) {
+ verifyMainTestThread(player);
+
+ if (expectedTimeline != null && expectedTimeline.equals(player.getCurrentTimeline())) {
+ return expectedTimeline;
+ }
+
+ AtomicReference<Timeline> receivedTimeline = new AtomicReference<>();
+ Player.EventListener listener =
+ new Player.EventListener() {
+ @Override
+ public void onTimelineChanged(Timeline timeline, int reason) {
+ if (expectedTimeline == null || expectedTimeline.equals(timeline)) {
+ receivedTimeline.set(timeline);
+ }
+ player.removeListener(this);
+ }
+ };
+ player.addListener(listener);
+ runUntil(() -> receivedTimeline.get() != null);
+ return receivedTimeline.get();
+ }
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player} calls the {@link
+ * Player.EventListener#onPositionDiscontinuity} callback with the specified {@link
+ * Player.DiscontinuityReason}.
+ */
+ public static void runUntilPositionDiscontinuity(
+ SimpleExoPlayer player, @Player.DiscontinuityReason int expectedReason) {
+ AtomicBoolean receivedCallback = new AtomicBoolean(false);
+ Player.EventListener listener =
+ new Player.EventListener() {
+ @Override
+ public void onPositionDiscontinuity(int reason) {
+ if (reason == expectedReason) {
+ receivedCallback.set(true);
+ player.removeListener(this);
+ }
+ }
+ };
+ player.addListener(listener);
+ runUntil(() -> receivedCallback.get());
+ }
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player} calls the {@link
+ * Player.EventListener#onPlayerError} callback.
+ *
+ * @param player The {@link SimpleExoPlayer}.
+ * @return The raised error.
+ */
+ public static ExoPlaybackException runUntilError(SimpleExoPlayer player) {
+ verifyMainTestThread(player);
+ AtomicReference<ExoPlaybackException> receivedError = new AtomicReference<>();
+ Player.EventListener listener =
+ new Player.EventListener() {
+ @Override
+ public void onPlayerError(ExoPlaybackException error) {
+ receivedError.set(error);
+ player.removeListener(this);
+ }
+ };
+ player.addListener(listener);
+ runUntil(() -> receivedError.get() != null);
+ return receivedError.get();
+ }
+
+ /**
+ * Run tasks of the main {@link Looper} until the {@code player} calls the {@link
+ * com.google.android.exoplayer2.video.VideoRendererEventListener#onRenderedFirstFrame} callback.
+ */
+ public static void runUntilRenderedFirstFrame(SimpleExoPlayer player) {
+ verifyMainTestThread(player);
+ AtomicBoolean receivedCallback = new AtomicBoolean(false);
+ VideoListener listener =
+ new VideoListener() {
+ @Override
+ public void onRenderedFirstFrame() {
+ receivedCallback.set(true);
+ player.removeVideoListener(this);
+ }
+ };
+ player.addVideoListener(listener);
+ runUntil(() -> receivedCallback.get());
+ }
+
+ /** Run tasks of the main {@link Looper} until the {@code condition} returns {@code true}. */
+ public static void runUntil(Supplier<Boolean> condition) {
+ verifyMainTestThread();
+
+ try {
+ while (!condition.get()) {
+ runOneTaskMethod.invoke(shadowLooper);
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void verifyMainTestThread(SimpleExoPlayer player) {
+ if (Looper.myLooper() != Looper.getMainLooper()
+ || player.getApplicationLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException();
+ }
+ }
+
+ private static void verifyMainTestThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
index 1fb3e4f..a86fe07 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java
@@ -34,6 +34,7 @@
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
@@ -42,6 +43,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
@@ -339,11 +341,10 @@
* @return The extracted {@link SeekMap}.
* @throws IOException If an error occurred reading from the input, or if the extractor finishes
* reading from input without extracting any {@link SeekMap}.
- * @throws InterruptedException If the thread was interrupted.
*/
public static SeekMap extractSeekMap(
Extractor extractor, FakeExtractorOutput output, DataSource dataSource, Uri uri)
- throws IOException, InterruptedException {
+ throws IOException {
ExtractorInput input = getExtractorInputFromPosition(dataSource, /* position= */ 0, uri);
extractor.init(output);
PositionHolder positionHolder = new PositionHolder();
@@ -380,11 +381,9 @@
* @return The {@link FakeTrackOutput} containing the extracted samples.
* @throws IOException If an error occurred reading from the input, or if the extractor finishes
* reading from input without extracting any {@link SeekMap}.
- * @throws InterruptedException If the thread was interrupted.
*/
public static FakeExtractorOutput extractAllSamplesFromFile(
- Extractor extractor, Context context, String fileName)
- throws IOException, InterruptedException {
+ Extractor extractor, Context context, String fileName) throws IOException {
byte[] data = TestUtil.getByteArray(context, fileName);
FakeExtractorOutput expectedOutput = new FakeExtractorOutput();
extractor.init(expectedOutput);
@@ -426,7 +425,7 @@
DataSource dataSource,
FakeTrackOutput trackOutput,
Uri uri)
- throws IOException, InterruptedException {
+ throws IOException {
int numSampleBeforeSeek = trackOutput.getSampleCount();
SeekMap.SeekPoints seekPoints = seekMap.getSeekPoints(seekTimeUs);
@@ -466,11 +465,22 @@
/** Returns an {@link ExtractorInput} to read from the given input at given position. */
public static ExtractorInput getExtractorInputFromPosition(
DataSource dataSource, long position, Uri uri) throws IOException {
- DataSpec dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, /* key= */ null);
+ DataSpec dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET);
long length = dataSource.open(dataSpec);
if (length != C.LENGTH_UNSET) {
length += position;
}
return new DefaultExtractorInput(dataSource, position, length);
}
+
+ /**
+ * Create a new {@link MetadataInputBuffer} and copy {@code data} into the backing {@link
+ * ByteBuffer}.
+ */
+ public static MetadataInputBuffer createMetadataInputBuffer(byte[] data) {
+ MetadataInputBuffer buffer = new MetadataInputBuffer();
+ buffer.data = ByteBuffer.allocate(data.length).put(data);
+ buffer.data.flip();
+ return buffer;
+ }
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java
index f3ec47a..7d31bc7 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java
@@ -22,6 +22,8 @@
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
+import com.google.android.exoplayer2.util.Assertions;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Unit test for {@link Timeline}. */
public final class TimelineAsserts {
@@ -48,7 +50,8 @@
* @param expectedWindowTags A list of expected window tags. If a tag is unknown or not important
* {@code null} can be passed to skip this window.
*/
- public static void assertWindowTags(Timeline timeline, Object... expectedWindowTags) {
+ public static void assertWindowTags(
+ Timeline timeline, @NullableType Object... expectedWindowTags) {
Window window = new Window();
assertThat(timeline.getWindowCount()).isEqualTo(expectedWindowTags.length);
for (int i = 0; i < timeline.getWindowCount(); i++) {
@@ -140,8 +143,9 @@
expectedWindowIndex++;
}
assertThat(period.windowIndex).isEqualTo(expectedWindowIndex);
- assertThat(timeline.getIndexOfPeriod(period.uid)).isEqualTo(i);
- assertThat(timeline.getUidOfPeriod(i)).isEqualTo(period.uid);
+ Object periodUid = Assertions.checkNotNull(period.uid);
+ assertThat(timeline.getIndexOfPeriod(periodUid)).isEqualTo(i);
+ assertThat(timeline.getUidOfPeriod(i)).isEqualTo(periodUid);
for (int repeatMode : REPEAT_MODES) {
if (i < accumulatedPeriodCounts[expectedWindowIndex + 1] - 1) {
assertThat(timeline.getNextPeriodIndex(i, period, window, repeatMode, false))
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/package-info.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/package-info.java
new file mode 100644
index 0000000..de69222
--- /dev/null
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+package com.google.android.exoplayer2.testutil;
+
+import com.google.android.exoplayer2.util.NonNullApi;
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java
index f5506de..a980254 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java
@@ -39,6 +39,7 @@
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
import com.google.android.exoplayer2.text.span.RubySpan;
import com.google.android.exoplayer2.util.Util;
+import com.google.common.truth.Fact;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import java.util.ArrayList;
@@ -84,9 +85,7 @@
Object[] spans = actual.getSpans(0, actual.length(), Object.class);
if (spans.length > 0) {
failWithoutActual(
- simpleFact("Expected no spans"),
- fact("in text", actual),
- fact("but found", getAllSpansAsString(actual)));
+ simpleFact("Expected no spans"), fact("in text", actual), actualSpansFact());
}
}
@@ -615,7 +614,7 @@
"Found unexpected %ss between start=%s,end=%s",
spanClazz.getSimpleName(), start, end)),
simpleFact("expected none"),
- fact("but found", getAllSpansAsString(actual)));
+ actualSpansFact());
}
}
@@ -641,7 +640,17 @@
simpleFact("No matching span found"),
fact("in text", actual),
fact("expected", getSpanAsString(start, end, spanType, spannedSubstring)),
- fact("but found", getAllSpansAsString(actual)));
+ actualSpansFact());
+ }
+
+ @RequiresNonNull("actual")
+ private Fact actualSpansFact() {
+ String actualSpans = getAllSpansAsString(actual);
+ if (actualSpans.isEmpty()) {
+ return Fact.simpleFact("but found no spans");
+ } else {
+ return Fact.fact("but found", actualSpans);
+ }
}
private static String getAllSpansAsString(Spanned spanned) {
diff --git a/tree/testutils/src/main/res/layout/exo_testutils_host_activity.xml b/tree/testutils/src/main/res/layout/exo_testutils_host_activity.xml
index e0988d4..44119e9 100644
--- a/tree/testutils/src/main/res/layout/exo_testutils_host_activity.xml
+++ b/tree/testutils/src/main/res/layout/exo_testutils_host_activity.xml
@@ -25,4 +25,8 @@
android:layout_height="match_parent"
android:layout_gravity="center"/>
+ <FrameLayout android:id="@+id/overlay_frame_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
</FrameLayout>
diff --git a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java
index c035196..e475e93 100644
--- a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java
+++ b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java
@@ -34,28 +34,18 @@
public final class FakeAdaptiveDataSetTest {
private static final Format[] TEST_FORMATS = {
- Format.createVideoSampleFormat(
- null,
- MimeTypes.VIDEO_H264,
- null,
- 1000000,
- Format.NO_VALUE,
- 1280,
- 720,
- Format.NO_VALUE,
- null,
- null),
- Format.createVideoSampleFormat(
- null,
- MimeTypes.VIDEO_H264,
- null,
- 300000,
- Format.NO_VALUE,
- 640,
- 360,
- Format.NO_VALUE,
- null,
- null)
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(1_000_000)
+ .setWidth(1280)
+ .setHeight(720)
+ .build(),
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(300_000)
+ .setWidth(640)
+ .setHeight(360)
+ .build()
};
private static final TrackGroup TRACK_GROUP = new TrackGroup(TEST_FORMATS);
diff --git a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
index c82980d..666e45c 100644
--- a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
+++ b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
@@ -89,7 +89,7 @@
@Test
public void testPostDelayed() {
- HandlerThread handlerThread = new HandlerThread("FakeClockTest thread");
+ HandlerThread handlerThread = new HandlerThread("FakeClockTest");
handlerThread.start();
FakeClock fakeClock = new FakeClock(0);
HandlerWrapper handler =
diff --git a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java
index 9af0c6e..b5ecf54 100644
--- a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java
+++ b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java
@@ -69,7 +69,7 @@
@Test
public void testReadPartialOpenEnded() throws IOException {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
- assertThat(dataSource.open(new DataSpec(uri, 7, C.LENGTH_UNSET, null))).isEqualTo(8);
+ assertThat(dataSource.open(new DataSpec(uri, 7, C.LENGTH_UNSET))).isEqualTo(8);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(3);
assertBuffer(TEST_DATA_PART_1, 7, 3);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(5);
@@ -81,7 +81,7 @@
@Test
public void testReadPartialBounded() throws IOException {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
- assertThat(dataSource.open(new DataSpec(uri, 9, 3, null))).isEqualTo(3);
+ assertThat(dataSource.open(new DataSpec(uri, 9, 3))).isEqualTo(3);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(1);
assertBuffer(TEST_DATA_PART_1, 9, 1);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(2);
@@ -89,7 +89,7 @@
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
- assertThat(dataSource.open(new DataSpec(uri, 11, 4, null))).isEqualTo(4);
+ assertThat(dataSource.open(new DataSpec(uri, 11, 4))).isEqualTo(4);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(4);
assertBuffer(TEST_DATA_PART_2, 1, 4);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
@@ -143,7 +143,7 @@
assertThat(e).hasMessageThat().isEqualTo(errorMessage);
}
dataSource.close();
- assertThat(dataSource.open(new DataSpec(uri, 15, 15, null))).isEqualTo(15);
+ assertThat(dataSource.open(new DataSpec(uri, 15, 15))).isEqualTo(15);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
@@ -220,7 +220,7 @@
.appendReadData(TestUtil.buildTestData(10))
.endData());
try {
- dataSource.open(new DataSpec(uri, 5, 10, null));
+ dataSource.open(new DataSpec(uri, 5, 10));
fail("IOException expected.");
} catch (IOException e) {
// Expected.